springboot如何在启动时执行指定方法

一、需求

有时候我们希望在springboot启动的时候执行一段代码完成一个特定任务,比如打印个springboot启动成功,又或者给其他项目发个通知说我启动完成啦。这个时候可以使用以下方法

  • 实现CommandLineRunner接口
  • 实现ApplicationRunner接口
  • 实现ApplicationListener接口
  • @PostConstruct
  • 实现InitializingBean接口

下面我们在讲讲具体怎么使用上述方法!

二、实现CommandLineRunner接口

先上代码,如下即可。

1
2
3
4
5
6
7
8
9
@Component  
public class CommandRun implements CommandLineRunner {

@Override
public void run(String... args) throws Exception {
System.out.println("CommandRun2222");
}

}

实现CommandLineRunner接口 然后在run方法里面调用需要调用的方法即可,好处是方法执行时,项目已经初始化完毕,是可以正常提供服务的。

同时该方法也可以接受参数,可以根据项目启动时: java -jar demo.jar arg1 arg2 arg3 传入的参数进行一些处理。

三、实现ApplicationRunner接口

先上代码,如下即可。

1
2
3
4
5
6
7
8
9
10
11
12
@Component  
public class AppStartRun implements ApplicationRunner {

@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("AppStartRun1111");
while (true){
TimeUnit.SECONDS.sleep(1);
}
}

}

实现ApplicationRunner接口和实现CommandLineRunner接口基本是一样的。

唯一的不同是启动时传参的格式,CommandLineRunner对于参数格式没有任何限制,ApplicationRunner接口参数格式必须是:–key=value

四、实现ApplicationListener接口

ApplicationListener的详细介绍可以看这里:

ApplicationListener是Spring事件机制的一部分,与抽象类ApplicationEvent类配合来完成ApplicationContext的事件机制。

如果容器中存在ApplicationListener的Bean,当ApplicationContext调用publishEvent方法时,对应的Bean会被触发。这一过程是典型的观察者模式的实现。

我们这里需要监听的是 ApplicationStartedEvent 事件,代码如下。

1
2
3
4
5
6
7
8
9
@Component  
public class AppStartListener implements ApplicationListener<ApplicationStartedEvent> {


@Override
public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
System.out.println("AppStartListener1111");
}
}

五、@PostConstruct

使用注解@PostConstruct是最常见的一种方式,存在的问题是如果执行的方法耗时过长,会导致项目在方法执行期间无法提供服务。

也就是说,在执行@postConstruct的时候,springboot并没有启动完成。还处在容器初始化阶段

1
2
3
4
5
6
7
8
9
10
11
@Component
public class StartInit {
//
// @Autowired 可以注入bean
// ISysUserService userService;
@PostConstruct
public void init() throws InterruptedException {
Thread.sleep(10*1000);//这里如果方法执行过长会导致项目一直无法提供服务
System.out.println(123456);
}
}

六、实现InitializingBean接口

实现InitializingBean接口的方式和使用注解@PostConstruct的方式两者差球不多。

同样的在执行 InitializingBean 接口的方法时,springboot并没有启动完成。还处在容器初始化阶段

1
2
3
4
5
6
7
@Component  
public class BeanInit implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("BeanInit1111");
}
}

七、在启动方法中编写

这个不讲废话了。

1
2
3
4
5
6
7
8
9
@SpringBootApplication  
public class TomcatDemoApplication {

public static void main(String[] args) {
SpringApplication.run(TomcatDemoApplication.class, args);
System.out.println("SpringApplication.run - 1111");
}

}

八、六者的执行顺序与原理

执行顺序如下:

1
2
3
4
5
6
PostConstruct - 1111
InitializingBean - 1111
ApplicationListener<ApplicationStartedEvent> - 1111
ApplicationRunner - 1111
CommandLineRunner - 1111
SpringApplication.run - 1111

PostConstruct和InitializingBean是在容器初始化时调用。PostConstruct 在 InitializingBean 之前。

ApplicationListener《ApplicationStartedEvent》 监听的是 ApplicationStartedEvent 事件,该事件是在容器初始化完成,可以开始对外提供服务的时候调用。

ApplicationRunner和CommandLineRunner是在容器初始化完成,可以对外提供服务之后进行执行。他们之间可以使用Order注解或实现Ordered接口来指定执行顺序,值越小越先执行。

最后就是SpringApplication.run了。

九、注意事项

切勿在上述方法中编写会阻塞、死循环的代码,因为他们都是在主线程中运行。

这里说一个例子,在CommandLineRunner中写一个死循环,使用idea或者jar包启动的时候依然可以正常的对外提供服务,但是如果你使用外置tomcat部署,服务是不能正常访问的。因为tomcat会等所有的应用启动完成之后在开放服务访问,而CommandLineRunner导致springboot会一直处于启动中状态,tomcat从而无法对外提供服务。