0


springboot项目常用的初始化方式,看看你知道几个?

一、前言

 平常的项目开发中,经常会遇到数据初始化的需求,比如说在项目启动之后需要读取自定义的配置信息、初始化自定义对象信息等等,那springboot项目中进行初始化方式有哪些,今天就一起来聊一下.为方便小伙伴查阅,第二个章节已经将各种方式进行了实现,需要用到的小伙伴可以直接拿去用。至于为什么能那样做,翻阅了相关官方文档,会做出简要说明,感兴趣的小伙伴可以看下第三个章节。

二、springboot项目初始化方式实战篇

1.ApplicationRunner

@ComponentpublicclassPersonalApplicationRunnerimplementsApplicationRunner{@Overridepublicvoidrun(ApplicationArguments args)throwsException{System.out.println("spring容器初始化方式:ApplicationRunner");}}

2.CommandLineRunner

@ComponentpublicclassPersonalCommandLineRunnerimplementsCommandLineRunner{@Overridepublicvoidrun(String... args)throwsException{System.out.println("spring容器初始化方式:CommandLineRunner");}}

3.ApplicationListener监听ContextRefreshedEvent事件

@ComponentpublicclassPersonalContextRefreshedEventimplementsApplicationListener<ContextRefreshedEvent>{@OverridepublicvoidonApplicationEvent(ContextRefreshedEvent contextRefreshedEvent){System.out.println("spring容器初始化方式:ApplicationListener<ContextRefreshedEvent>");}}

4.InitializingBean

@ConfigurationpublicclassPersonalInitializingBeanimplementsInitializingBean{@OverridepublicvoidafterPropertiesSet()throwsException{System.out.println("spring容器初始化方式:PersonalInitializingBean");}}

5.@PostConstruct

@ConfigurationpublicclassPersonalInitializingBeanimplementsInitializingBean{@OverridepublicvoidafterPropertiesSet()throwsException{System.out.println("spring容器初始化方式:PersonalInitializingBean");}}

6.静态代码块

@ComponentpublicclassPersonalStacticInit{static{System.out.println("静态代码块执行。。。。。。。。");}}

二、各种初始化操作的执行顺序

  项目启动,控制台输出内容如下:

在这里插入图片描述

三、从官方文档看各种初始化操作

1.ApplicationRunner与CommandLineRunner

 springboot项目启动类中run方法执行逻辑实际上是执行
SpringApplication

中run方法:

publicConfigurableApplicationContextrun(String... args){//计时工具StopWatch stopWatch =newStopWatch();
        stopWatch.start();ConfigurableApplicationContext context =null;Collection<SpringBootExceptionReporter> exceptionReporters =newArrayList();this.configureHeadlessProperty();//第一步,获取并启动监听器SpringApplicationRunListeners listeners =this.getRunListeners(args);
        listeners.starting();Collection exceptionReporters;try{ApplicationArguments applicationArguments =newDefaultApplicationArguments(args);//第二步,根据SpringApplicationRunListeners以及参数来准备环境ConfigurableEnvironment environment =this.prepareEnvironment(listeners, applicationArguments);this.configureIgnoreBeanInfo(environment);//准备Banner打印器‐就是启动Spring Boot的时候在console上的ASCII艺术字体Banner printedBanner =this.printBanner(environment);//第三步:创建Spring容器
            context =this.createApplicationContext();
            exceptionReporters =this.getSpringFactoriesInstances(SpringBootExceptionReporter.class,newClass[]{ConfigurableApplicationContext.class}, context);//第四步:Spring容器前置处理this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);//第五步:刷新容器this.refreshContext(context);//第六步:Spring容器后置处理this.afterRefresh(context, applicationArguments);
            stopWatch.stop();if(this.logStartupInfo){(newStartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);}//第七步:遍历容器中的监听器,执行监听器逻辑
            listeners.started(context);//第八步:执行Runnersthis.callRunners(context, applicationArguments);}catch(Throwable var10){this.handleRunFailure(context, var10, exceptionReporters, listeners);thrownewIllegalStateException(var10);}try{// 第九步:遍历容器中的监听器,执行监听器逻辑
            listeners.running(context);//返回容器return context;}catch(Throwable var9){this.handleRunFailure(context, var9, exceptionReporters,(SpringApplicationRunListeners)null);thrownewIllegalStateException(var9);}}

代码看的不直观看下启动的流程图:
在这里插入图片描述

 其中第8的个步骤是执行Runners,具体到代码:
callRunners(context, applicationArguments)

就是调用

ApplicationRunner

或是

CommandLineRunner

,具体调用逻辑如下:

privatevoidcallRunners(ApplicationContext context,ApplicationArguments args){// 组装runnerList<Object> runners =newArrayList();// 组装ApplicationRunner类型的runner       
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());// 组装CommandLineRunner类型的runner  
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());// 组装的runner集合进行排序AnnotationAwareOrderComparator.sort(runners);Iterator var4 =(newLinkedHashSet(runners)).iterator();while(var4.hasNext()){Object runner = var4.next();// 执行ApplicationRunner实现类逻辑if(runner instanceofApplicationRunner){this.callRunner((ApplicationRunner)runner, args);}// 执行CommandLineRunner实现类逻辑if(runner instanceofCommandLineRunner){this.callRunner((CommandLineRunner)runner, args);}}}
callRunner

方法用来执行

CommandLineRunner

或是

ApplicationRunner

实现类的run逻辑内容,具体实现如下:

privatevoidcallRunner(ApplicationRunner runner,ApplicationArguments args){try{(runner).run(args);}catch(Exception ex){thrownewIllegalStateException("Failed to execute ApplicationRunner", ex);}}

看到这里会发现有

ApplicationRunner、CommandLineRunner

,下面分别说下两个的区别:

ApplicationRunner

中run方法

publicinterfaceApplicationRunner{voidrun(ApplicationArguments var1)throwsException;}

看下官方对的说明:

Interface used to indicate that a bean should run when it is contained within a {@link SpringApplication}.
Multiple {@link ApplicationRunner} beans can be defined within the same application context and can be ordered using the {@link Ordered} interface or {@link Order @Order} annotation.

翻译一下就是:

ApplicationRunner这个接口主要用于执行bean,当bean在SpringApplication中.
SpringApplication应该不陌生,springboot项目启动类中main方法就是执行的SpringApplication.run().所以说ApplicationRunner就是为了springboot项目启动过程中执行指定业务逻辑准备的接口.
如果存在多个ApplicationRunner接口的实现类,可以通过@Ordered注解指定执行顺序.

看下CommandLineRunner官方说明:

Interface used to indicate that a bean should run 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. If you need access to {@link ApplicationArguments} instead of the raw String array consider using {@link ApplicationRunner}.

 看完会发现关于CommandLineRunner的说明和ApplicationRunner基本一致,也是用于springboot项目启动用于执行指定业务逻辑准备的接口.唯一的区别就是传递参数不同,官方文档中还明确说明,如果在想获取ApplicationArguments(项目参数信息),推荐使用ApplicationRunner.

CommandLineRunner中run方法

publicinterfaceCommandLineRunner{voidrun(String... var1)throwsException;}

3.ApplicationListener监听ContextRefreshedEvent事件使用说明

  上文中讲到SpringApplication中run方法,其中第五步是刷新容器,具体刷新的业务处理是在AbstractApplicationContext中refresh方法中完成,其中最后一个操作就是执行发布容器中的事件. AbstractApplicationContext中refresh方法 
publicvoidrefresh()throwsBeansException,IllegalStateException{synchronized(this.startupShutdownMonitor){// 省略部分逻辑// 发布容器中定义的各种事件finishRefresh();}
AbstractApplicationContext

中finishRefresh具体的执行逻辑如下:

protectedvoidfinishRefresh(){// 省略部分逻辑// 发布ContextRefreshedEvent事件publishEvent(newContextRefreshedEvent(this));// 省略部分逻辑}

继续往下看,最终调用的监听事件逻辑:

privatevoiddoInvokeListener(ApplicationListener listener,ApplicationEvent event){try{// 执行监听器实现类中onApplicationEvent具体实现逻辑
            listener.onApplicationEvent(event);}catch(ClassCastException ex){// 省略部分逻辑}}
 总结一下:项目启动过程中需要进行容器刷新操作,在容器刷新结束之后会发布
ContextRefreshedEvent

事件,自定义的监听器监听到该事件就会执行自定义的业务逻辑处理.也就想做数据初始化自定义监听器,要在实现

ApplicationListener

的时候必须指定监听事件为

ContextRefreshedEvent

的原因.

4.InitializingBean使用说明

  关于InitializingBean看下官方介绍: 

Interface to be implemented by beans that need to react once all their properties have been set by a {@link BeanFactory}: e.g. to perform custom initialization, or merely to check that all mandatory
properties have been set.

用自己的话翻译一下:

InitializingBean

接口用于执行一次当bean的各种属性设置完成之后.每个bean对象会经历实例化、初始化的过程,初始化过程可以简单理解为设置属性过程,适用场景:执行自定义初始化设置或是检查属性是否被设置.当然目前使用到最多的是初始化设置.
首先看下bean创建的核心逻辑:AbstractAutowireCapableBeanFactory中doCreateBean方法:

protectedObjectdoCreateBean(String beanName,RootBeanDefinition mbd,@NullableObject[] args)throwsBeanCreationException{// 省略部分逻辑// 处理bean的依赖注入populateBean(beanName, mbd, instanceWrapper);// 实例化bean处理          
    exposedObject =initializeBean(beanName, exposedObject, mbd);// 省略部分逻辑return exposedObject;}

继续看

initializeBean

中的执行逻辑,最终执行的是初始化方法

invokeInitMethods

,

AbstractAutowireCapableBeanFactory

initializeBean

方法:

protectedObjectinitializeBean(String beanName,Object bean,@NullableRootBeanDefinition mbd){// 省略部分逻辑// 执行初始化方法invokeInitMethods(beanName, wrappedBean, mbd);// 省略部分逻辑return wrappedBean;}

看下

invokeInitMethods

方法执行的具体逻辑就会发现此逻辑实际上执行的就是

InitializingBean

中的

afterPropertiesSet

,

AbstractAutowireCapableBeanFactory

invokeInitMethods

方法:

protectedvoidinvokeInitMethods(String beanName,Object bean,@NullableRootBeanDefinition mbd)throwsThrowable{// 省略部分逻辑// SecurityManager是JAVA安全管理器当运行未知的Java程序的时候,该程序可能有恶意代码(删除系统文件、重启系统等),为了防止运行恶意代码对系统产生影响,需要对运行的代码的权限进行控制,这时候就要启用Java安全管理器。if(System.getSecurityManager()!=null){AccessController.doPrivileged((PrivilegedExceptionAction<Object>)()->{((InitializingBean) bean).afterPropertiesSet();returnnull;},getAccessControlContext());}else{((InitializingBean) bean).afterPropertiesSet();}}// 省略部分逻辑}

也就是说

InitializingBean

中的

afterPropertiesSet

会在对象实例化设置完成属性之后才会执行.

5.@PostConstruct使用说明

bean创建的核心逻辑:

在这里插入图片描述

AbstractAutowireCapableBeanFactory

initializeBean

方法:

protectedObjectinitializeBean(finalString beanName,finalObject bean,@NullableRootBeanDefinition mbd){// ...Object wrappedBean = bean;if(mbd ==null||!mbd.isSynthetic()){// 初始化前置处理
        wrappedBean =applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);}try{// 调用初始化方法invokeInitMethods(beanName, wrappedBean, mbd);}catch(Throwable ex){// ...}if(mbd ==null||!mbd.isSynthetic()){// 初始化后置处理
        wrappedBean =applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);}return wrappedBean;}

调试可知

@PostConstruct

是在

applyBeanPostProcessorsBeforeInitialization

初始化前置中进行处理的,看下具体实现:

@OverridepublicObjectapplyBeanPostProcessorsBeforeInitialization(Object existingBean,String beanName)throwsBeansException{Object result = existingBean;for(BeanPostProcessor processor :getBeanPostProcessors()){Object current = processor.postProcessBeforeInitialization(result, beanName);if(current ==null){return result;}
            result = current;}return result;}

将各种容器中各种BeanPostProcessors后置处理器进行遍历,依次执行前置初始化逻辑,

@PostConstruct

是由

InitDestroyAnnotationBeanPostProcessor

中进行的初始化处理,看这个后置处理器的名称就可以看出是用于处理初始化以及销毁操作的后置处理器.看下具体的实现:

@OverridepublicObjectpostProcessBeforeInitialization(Object bean,String beanName)throwsBeansException{// 解析元数据LifecycleMetadata metadata =findLifecycleMetadata(bean.getClass());// 反射执行@PostConstruct或是@PreDestroy所在的方法
        metadata.invokeInitMethods(bean, beanName);return bean;}

具体解析的逻辑在

buildLifecycleMetadata

privateLifecycleMetadatabuildLifecycleMetadata(finalClass<?> clazz){List<LifecycleElement> initMethods =newArrayList<>();List<LifecycleElement> destroyMethods =newArrayList<>();Class<?> targetClass = clazz;do{finalList<LifecycleElement> currInitMethods =newArrayList<>();finalList<LifecycleElement> currDestroyMethods =newArrayList<>();ReflectionUtils.doWithLocalMethods(targetClass, method ->{if(this.initAnnotationType !=null&& method.isAnnotationPresent(this.initAnnotationType)){LifecycleElement element =newLifecycleElement(method);
                    currInitMethods.add(element);}if(this.destroyAnnotationType !=null&& method.isAnnotationPresent(this.destroyAnnotationType)){
                    currDestroyMethods.add(newLifecycleElement(method));}});// 省略部分逻辑}}

可以看出来这里主要是用initAnnotationType或是destroyAnnotationType进行组装参数,至于这两个具体是什么可以继续往下看怎么设置的,以initAnnotationType为例,initAnnotationType进行设置的逻辑是

publicvoidsetDestroyAnnotationType(Class<?extendsAnnotation> destroyAnnotationType){this.destroyAnnotationType = destroyAnnotationType;}

具体调用的逻辑发现会在
CommonAnnotationBeanPostProcessor创建过程中会初始化initAnnotationType

publicCommonAnnotationBeanPostProcessor(){setOrder(Ordered.LOWEST_PRECEDENCE -3);setInitAnnotationType(PostConstruct.class);setDestroyAnnotationType(PreDestroy.class);ignoreResourceType("javax.xml.ws.WebServiceContext");}
 关于@PostConstruct执行原理总结一下:spring项目启动加载bean过程中会将带有@PostConstruct、@PreDestroy的bean信息或是方法信息注册到InitDestroyAnnotationBeanPostProcessor中的initAnnotationType或是destroyAnnotationType,然后InitDestroyAnnotationBeanPostProcessor中postProcessBeforeInitialization会查找出带有@PostConstruct、@PreDestroy注解的方法信息,然后用反射的方式执行方法中的逻辑。
  以上是关于springboot中实现初始化操作的常用方式以及各种方式的使用原理说明,小伙伴们如果有其他的实现方式欢迎评论区留言,看完感觉有收获的别忘了点赞或是收藏一下.
标签: spring boot java spring

本文转载自: https://blog.csdn.net/weixin_43401380/article/details/127562183
版权归原作者 卖柴火的小伙子 所有, 如有侵权,请联系我们删除。

“springboot项目常用的初始化方式,看看你知道几个?”的评论:

还没有评论