Spring项目启动后加载数据的几种方式
做项目过程中,有时候难免会遇到一些需求需要项目启动完毕时初始化一些数据或执行一些特定的操作。
Spring启动后加载数据的几种方式:
项目基于SpringBoot开发的
1. org.springframework.boot.CommandLineRuner
CommandLineRuner的定义为一个接口。
CommandLineRuner接口
在项目中只需定义一个类,实现CommandLineRuner接口,然后在覆盖的run方法里进行自己的业务处理就可以了。
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception
{
// do something here
}
}
2. org.springframework.boot.ApplicationRunner
ApplicationRunner与CommandLineRunner 一样,也是一个接口类型
CommandLineRunner 接口
项目中使用也是定义一个类实现ApplicationRunner,然后覆盖run方法进行自己的业务处理。
@Component
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments applicationArguments) throws Exception
{
// do something here
}
}
ApplicationRunner与CommandLineRunner都是覆盖接口的run方法,在run方法里进行相应的业务处理。不同之处是CommandLineRunner的run方法参数为启动类main方法传进来原封不动的参数args,ApplicationRunner的run方法是对args 参数进行了多一层的封装ApplicationArguments args,可以获取更多的项目相关信息。
public interface ApplicationArguments {
// 返回传递给应用程序的原始未处理参数。
String[] getSourceArgs();
// 返回所有选项参数的名称。
Set getOptionNames();
// 返回从参数中解析的选项参数集是否包含具有给定名称的选项。
boolean containsOption(String name);
// 返回与具有给定名称的arguments选项关联的值的集合。
List getOptionValues(String name);
// 返回已解析的非选项参数的集合。
List getNonOptionArgs();
}
ApplicationRunner与CommandLineRunner都是在容器加载完毕后执行
源码分析:SpringApplicaton.run(String... args)方法
callRunners(context,applicationArguments);方法
从callRunners方法源码看得出,ApplicationRunner比CommandLineRunner先执行,但一般项目里实现其中一个就行。如果有多个ApplicationRunner或CommandLineRunner的实现类,又需要按顺序执行的话,可以在实现类头上加注解@Order(int),数值越小越早执行。
3. PostConstruct
PostConstruct是JDK提供的一个注解,只需在类成员方法上添加@PostConstruct注解,在执行Spring bean初始时就会执行该成员方法。
注:由于是在容器加载Bean的时候执行,有可能执行该方法时一些其他的Bean还没加载完,所有在注解PostConstruct的成员方法里不能依赖其他的Bean。
项目不是基于SpringBoot的,可以使用以下方法:
1. 实现org.springframework.beans.factory.InitializingBean接口,在afterPropertiesSet方法处理自己的业务。
@Component
public class MyInitializingBean implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
// do something here
}
}
从afterPropertiesSet的方法名可以看得出来,Spring容器在加载实例的时候设置实体属性后执行该方法。
源码分析:
在上一遍Spring的启动过程文章里有提到过Spring启动时会加载所有的Bean到容器
在解析XML配置文件定义的实例时是通过类DefaultBeanDefinitionDocumentReader的
void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法。
最后实际是通过BeanDefinitionReaderUtils工具类调用到具体的register的registerBeanDefinition。
这里的register具体实现类为DefaultLisableBeanFactory。
兜兜转转还是回到了DefaultLisableBeanFactory。
DefaultLisableBeanFactory是Spring容器BeanFactory的核心基础。
DefaultLisableBeanFactory.registerBeanDefinition()方法将实例加入到beanDefinitionNames中。
AbstractApplicationContext类的refresh()方法
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory)实例剩下所有的非懒加载的实例。
在这里调用DefaultListableBeanFactory类的preInstantiateSingletons()方法
根据beanName调用getBean()实例化对象
getBean() -> AbstractBeanFactory.getBean(String naem) ->doGetBean();
最终还是回到AbstractAutowireCapableBeanFactory.createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)方法
在doCreateBean()方法里调用initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd)方法
然后在invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)回调实现了InitializeBean接口的afterPropertiesSet方法
2. 实现ApplicationListener接口,监听ContextRefreshedEvent事件。
@Component
public class MyApplicationListener implements ApplicationListener {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//保证在web容器中只执行一次该事件
if (event.getApplicationContext().getParent() == null) {
// do something here
}
}
}
注意:在Spring mvc web项目中,系统会存在两个容器,一个是root application context,另一个是自己项目的projectName-servlet context,是作为root aplication context的子容器,所以会触发两次ContextRefreshedEvent事件,我们只有 root aplication context 初始化完成后进行相应的业务处理就可以了,即event.getApplicationContext().getParent() == null。
源码分析:
AbstractApplicationContext.refresh()方法,在初始化完所有的非懒加载的实例后调用finishRefresh()方法;
在这里会触发一个ContextRefreshedEvent事件。监听器监听到事件就会调用所有实现了ApplicationListener监听ContextRefreshedEvent的方法。
3. 在配置文件里 标签里指定init-method方法
主要也是在DefaultBeanDefinitionDocumentReader里将bean标签定义的name解析成BeanDefinition,然后在initializeBean()的时候调用定义的init-method。这里就不细贴源码了。
总结:ApplicationRunner,CommandLineRunner,ApplicationListener是在容器初始化完成后调用,因此可以依懒于其他实例。PostConstruct注解是实例构造函数执行后调用,InitializingBean的afterPropertiesSet是在实例所有属性赋值后执行,两者都在容器初始化完成前执行,故不能依懒其他实例。
执行顺序:PostConstruct --> afterPropertiesSet --> ApplicationListener --> ApplicationRunner --> CommandLineRunner
以上是学习Spring源码的一个总结,由于个人能力有限,存在错误的地方欢迎评论指出,大家一起学习,谢谢。