项目中,经常需要在启动过程中初始化一些数据,如从数据库读取一些配置初始化,或从数据库读取一些热点数据到redis进行初始化缓存。
CommandLineRunner是Spring提供的接口,定义了一个run()方法,用于执行初始化操作。
import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class InitConfigCommand implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("CommandLineRunner:"+"{}"+"接口实现方式重写"); } }
CommandLineRunner的执行时机为Spring beans初始化之后,因此CommandLineRunner的执行一定是晚于@PostConstruct的。
若有多组初始化操作,则每一组操作都要定义一个CommandLineRunner派生类并实现run()方法。这些操作的执行顺序使用@Order(n)来设置,n为int型数据。
@Component @Order(99) public class CommandLineRunnerA implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("初始化:CommandLineRunnerA"); } } @Component @Order(1) public class CommandLineRunnerB implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("初始化:CommandLineRunnerB"); } }
如上,会先执行CommandLineRunnerB的run(),再执行CommandLineRunnerA的run()。
@Order(n)中的n较小的会先执行,较大的后执行。n只要是int值即可,无需顺序递增。
ApplicationRunner接口与CommandLineRunner接口类似,都需要实现run()方法。二者的区别在于run()方法的参数不同:
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; @Component public class InitConfig implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { System.out.println("项目启动初始化"); } }
ApplicationRunner接口的run()参数为ApplicationArguments对象,因此可以获取更多项目相关的内容。
ApplicationRunner接口与CommandLineRunner接口的调用时机也是相同的,都是Spring beans初始化之后。因此ApplicationRunner接口也使用@Order(n)来设置执行顺序。
对于注入到Spring容器中的类,在其成员函数前添加@PostConstruct注解,则在执行Spring beans初始化时,就会执行该函数。
但由于该函数执行时,其他Spring beans可能并未初始化完成,因此在该函数中执行的初始化操作应当不依赖于其他Spring beans。
@Component public class Construct { @PostConstruct public void doConstruct() throws Exception { System.out.println("初始化:PostConstruct"); } }
@PostConstruct注解使用在方法上,它可以被用来标注一个非静态的 void 方法,这个方法会在该类被 Spring 容器初始化后立即执行。因为它的执行时机是在依赖注入之后,对象构造完成之后,也就是说是在@Autowired注入之后执行。所以这里可以进行一些初始化操作,如某些需要在对象创建后才能进行的数据初始化操作。
需要注意以下几点:
假设我们有一个需要初始化数据的类:
public class InitService { private Listdata; public InitService() { this.data = Arrays.asList("A", "B", "C"); } @PostConstruct public void init() { data.add("D"); } public List getData() { return this.data; } }
当我们实例化 InitService 时,构造函数会为 data 属性赋初值,而 @PostConstruct 注解的 init 方法会在 Spring 容器实例化完 InitService 后被执行,将 “D” 添加到 data 列表中。所以当我们调用 getData() 方法时,返回的列表应该是 [A, B, C, D]。
接下来看看 @Autowired 和@PostConstruct 的具体执行顺序
@Service public class TestA { static { System.out.println("staticA"); } @Autowired private TestB testB; public TestA() { System.out.println("这是TestA 的构造方法"); } @PostConstruct private void init() { System.out.println("这是TestA的 init 方法"); testB.test(); } }
@Service public class TestB { static { System.out.println("staticB"); } @PostConstruct private void init() { System.out.println("这是TestB的init 方法"); } public TestB() { System.out.println("这是TestB的构造方法"); } void test() { System.out.println("这是TestB的test方法"); } }
构造方法:在对象初始化时执行。执行顺序在static静态代码块之后。
服务启动后,输出结果如下:
staticA 这是TestA 的构造方法 staticB 这是TestB的构造方法 这是TestB的init 方法 这是TestA的 init 方法 这是TestB的test方法
结论为:等@Autowired注入后,在执行@PostConstruct注解的方法。
static静态代码块,在类加载的时候即自动执行。
使用的static静态代码块,实现原理为@Component + static代码块, spring boot项目在启动过程中,会扫描@Component 并初始化相应的类,类的初始化过程会运行静态代码块。
@Component public class WordInitConfig { static { WordSegmenter.segWithStopWords("初始化分词"); } }
static>constructer>@Autowired>@PostConstruct>ApplicationRunner>CommandLineRunner