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()方法
SpringBoot在项目启动后会遍历所有实现CommandLineRunner的实体类并执行run方法,多个实现类可以并存并且根据order注解排序顺序执行。
同样是启动时执行定时任务,使用这种方式我的写法如下:
@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、 实现 @ApplicationRunner 接口并重写run()方法
run方法参数是ApplicationArguments,解析封装过后的args参数, 通过该对象既可以拿到原始命令行参数,也可以拿到解析后的参数, 其中 @Order中的值指定了执行顺序,值小的先执行。默认值是Integer.MAX_VALUE
import java.util.Arrays;
import java.util.Set;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(1)
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("====================MyApplicationRunner================");
        System.out.println("order值: 1");
        System.out.println("原始参数:"+Arrays.asList(args.getSourceArgs()));
        Set<String> keys = args.getOptionNames();
        for (String key : keys) {
            System.out.println("解析后的key: ["+key+"]  value: "+args.getOptionValues(key));
        }
        System.out.println("无OptionName的参数: "+args.getNonOptionArgs());
        System.out.println("=======================================================");
    }

4、实现org.springframework.beans.factory.InitializingBean接口并重写 afterPropertiesSet()方法 

InitializingBean接口只包含一个方法afterPropertiesSet(),凡是继承了InitializingBean接口的类,在初始化时都会调用这方法;
使用方法分为三个步骤:1、 被spring管理        2、 实现InitializingBean接口         3、重写afterPropertiesSet方法
@Component
public class InitializationBeanTest implements InitializingBean{
    private static final Logger LOG = LoggerFactory.getLogger(InitializationBeanTest.class);

    @Override
    public void afterPropertiesSet() throws Exception {
        LOG.info("InitializingBean 开始了。。。");
        //初始化操作
    }
}

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

ContextRefreshedEvent是Spring的ApplicationContextEvent一个实现,此事件会在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容器初始化完成后就会执行该方法。
        }
}

总结

在项目启动时执行代码的方式,以上几种方式在网络大搜集之后整合而来,方便大家查阅。
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值