SpringBoot 源码解析 (三)----- Spring Boot 精髓:启动时初始化数据
在我们用 springboot 搭建项目的时候,有时候会碰到在项目启动时初始化一些操作的需求 ,针对这种需求 spring boot为我们提供了以下几种方案供我们选择:
-
applicationrunner
与commandlinerunner
接口 -
spring容器初始化时initializingbean接口和@postconstruct
-
spring的事件机制
applicationrunner与commandlinerunner
我们可以实现 applicationrunner
或 commandlinerunner
接口, 这两个接口工作方式相同,都只提供单一的run方法,该方法在springapplication.run(…)完成之前调用,不知道大家还对我上一篇文章结尾有没有印象,我们先来看看这两个接口
public interface applicationrunner { void run(applicationarguments var1) throws exception; } public interface commandlinerunner { void run(string... var1) throws exception; }
都只提供单一的run方法,接下来我们来看看具体的使用
applicationrunner
构造一个类实现applicationrunner接口
//需要加入到spring容器中 @component public class applicationrunnertest implements applicationrunner { @override public void run(applicationarguments args) throws exception { system.out.println("applicationrunner"); } }
很简单,首先要使用@component将实现类加入到spring容器中,为什么要这样做我们待会再看,然后实现其run方法实现自己的初始化数据逻辑就可以了
commandlinerunner
对于这两个接口而言,我们可以通过order注解或者使用ordered接口来指定调用顺序, @order()
中的值越小,优先级越高
//需要加入到spring容器中 @component @order(1) public class commandlinerunnertest implements commandlinerunner { @override public void run(string... args) throws exception { system.out.println("commandlinerunner..."); } }
同样需要加入到spring容器中,commandlinerunner的参数是最原始的参数,没有进行任何处理,applicationrunner的参数是applicationarguments,是对原始参数的进一步封装
源码分析
大家回顾一下我上一篇文章,也就是springapplication.run方法的最后一步第八步:执行runners,这里我直接把代码复制过来
private void callrunners(applicationcontext context, applicationarguments args) { list<object> runners = new arraylist<object>(); //获取容器中所有的applicationrunner的bean实例 runners.addall(context.getbeansoftype(applicationrunner.class).values()); //获取容器中所有的commandlinerunner的bean实例 runners.addall(context.getbeansoftype(commandlinerunner.class).values()); annotationawareordercomparator.sort(runners); for (object runner : new linkedhashset<object>(runners)) { if (runner instanceof applicationrunner) { //执行applicationrunner的run方法 callrunner((applicationrunner) runner, args); } if (runner instanceof commandlinerunner) { //执行commandlinerunner的run方法 callrunner((commandlinerunner) runner, args); } } }
很明显,是直接从spring容器中获取applicationrunner和commandlinerunner的实例,并调用其run方法,这也就是为什么我要使用@component将applicationrunner和commandlinerunner接口的实现类加入到spring容器中了。
initializingbean
在spring初始化bean的时候,如果bean实现了 initializingbean
接口,在对象的所有属性被初始化后之后才会调用afterpropertiesset()方法
@component public class initialingzingbeantest implements initializingbean { @override public void afterpropertiesset() throws exception { system.out.println("initializingbean.."); } }
我们可以看出spring初始化bean肯定会在 applicationrunner和commandlinerunner接口调用之前。
@postconstruct
@component public class postconstructtest { @postconstruct public void postconstruct() { system.out.println("init..."); } }
我们可以看到,只用在方法上添加@postconstruct注解,并将类注入到spring容器中就可以了。我们来看看@postconstruct注解的方法是何时执行的
在spring初始化bean时,对bean的实例赋值时,populatebean方法下面有一个initializebean(beanname, exposedobject, mbd)方法,这个就是用来执行用户设定的初始化操作。我们看下方法体:
protected object initializebean(final string beanname, final object bean, @nullable rootbeandefinition mbd) { if (system.getsecuritymanager() != null) { accesscontroller.doprivileged((privilegedaction<object>) () -> { // 激活 aware 方法 invokeawaremethods(beanname, bean); return null; }, getaccesscontrolcontext()); } else { // 对特殊的 bean 处理:aware、beanclassloaderaware、beanfactoryaware invokeawaremethods(beanname, bean); } object wrappedbean = bean; if (mbd == null || !mbd.issynthetic()) { // 后处理器 wrappedbean = applybeanpostprocessorsbeforeinitialization(wrappedbean, beanname); } try { // 激活用户自定义的 init 方法 invokeinitmethods(beanname, wrappedbean, mbd); } catch (throwable ex) { throw new beancreationexception( (mbd != null ? mbd.getresourcedescription() : null), beanname, "invocation of init method failed", ex); } if (mbd == null || !mbd.issynthetic()) { // 后处理器 wrappedbean = applybeanpostprocessorsafterinitialization(wrappedbean, beanname); } return wrappedbean; }
我们看到会先执行后处理器然后执行invokeinitmethods方法,我们来看下applybeanpostprocessorsbeforeinitialization
public object applybeanpostprocessorsbeforeinitialization(object existingbean, string beanname) throws beansexception { object result = existingbean; for (beanpostprocessor beanprocessor : getbeanpostprocessors()) { result = beanprocessor.postprocessbeforeinitialization(result, beanname); if (result == null) { return result; } } return result; } public object applybeanpostprocessorsafterinitialization(object existingbean, string beanname) throws beansexception { object result = existingbean; for (beanpostprocessor beanprocessor : getbeanpostprocessors()) { result = beanprocessor.postprocessafterinitialization(result, beanname); if (result == null) { return result; } } return result; }
获取容器中所有的后置处理器,循环调用后置处理器的postprocessbeforeinitialization方法,这里我们来看一个beanpostprocessor
public class commonannotationbeanpostprocessor extends initdestroyannotationbeanpostprocessor implements instantiationawarebeanpostprocessor, beanfactoryaware, serializable { public commonannotationbeanpostprocessor() { this.setorder(2147483644); //设置初始化参数为postconstruct.class this.setinitannotationtype(postconstruct.class); this.setdestroyannotationtype(predestroy.class); this.ignoreresourcetype("javax.xml.ws.webservicecontext"); } //略... }
在构造器中设置了一个属性为postconstruct.class,再次观察commonannotationbeanpostprocessor这个类,它继承自initdestroyannotationbeanpostprocessor。initdestroyannotationbeanpostprocessor顾名思义,就是在bean初始化和销毁的时候所作的一个前置/后置处理器。查看initdestroyannotationbeanpostprocessor类下的postprocessbeforeinitialization方法:
public object postprocessbeforeinitialization(object bean, string beanname) throws beansexception { lifecyclemetadata metadata = findlifecyclemetadata(bean.getclass()); try { metadata.invokeinitmethods(bean, beanname); } catch (invocationtargetexception ex) { throw new beancreationexception(beanname, "invocation of init method failed", ex.gettargetexception()); } catch (throwable ex) { throw new beancreationexception(beanname, "couldn't invoke init method", ex); } return bean; } private lifecyclemetadata buildlifecyclemetadata(final class clazz) { final lifecyclemetadata newmetadata = new lifecyclemetadata(); final boolean debug = logger.isdebugenabled(); reflectionutils.dowithmethods(clazz, new reflectionutils.methodcallback() { public void dowith(method method) { if (initannotationtype != null) { //判断clazz中的methon是否有initannotationtype注解,也就是postconstruct.class注解 if (method.getannotation(initannotationtype) != null) { //如果有就将方法添加进lifecyclemetadata中 newmetadata.addinitmethod(method); if (debug) { logger.debug("found init method on class [" + clazz.getname() + "]: " + method); } } } if (destroyannotationtype != null) { //判断clazz中的methon是否有destroyannotationtype注解 if (method.getannotation(destroyannotationtype) != null) { newmetadata.adddestroymethod(method); if (debug) { logger.debug("found destroy method on class [" + clazz.getname() + "]: " + method); } } } } }); return newmetadata; }
在这里会去判断某方法是否有postconstruct.class注解,如果有,则添加到init/destroy队列中,后续一一执行。@postconstruct注解的方法会在此时执行,我们接着来看invokeinitmethods
protected void invokeinitmethods(string beanname, final object bean, @nullable rootbeandefinition mbd) throws throwable { // 是否实现 initializingbean // 如果实现了 initializingbean 接口,则只掉调用bean的 afterpropertiesset() boolean isinitializingbean = (bean instanceof initializingbean); if (isinitializingbean && (mbd == null || !mbd.isexternallymanagedinitmethod("afterpropertiesset"))) { if (logger.isdebugenabled()) { logger.debug("invoking afterpropertiesset() on bean with name '" + beanname + "'"); } if (system.getsecuritymanager() != null) { try { accesscontroller.doprivileged((privilegedexceptionaction<object>) () -> { ((initializingbean) bean).afterpropertiesset(); return null; }, getaccesscontrolcontext()); } catch (privilegedactionexception pae) { throw pae.getexception(); } } else { // 直接调用 afterpropertiesset() ((initializingbean) bean).afterpropertiesset(); } } if (mbd != null && bean.getclass() != nullbean.class) { // 判断是否指定了 init-method(), // 如果指定了 init-method(),则再调用制定的init-method string initmethodname = mbd.getinitmethodname(); if (stringutils.haslength(initmethodname) && !(isinitializingbean && "afterpropertiesset".equals(initmethodname)) && !mbd.isexternallymanagedinitmethod(initmethodname)) { // 利用反射机制执行 invokecustominitmethod(beanname, bean, mbd); } } }
首先检测当前 bean 是否实现了 initializingbean 接口,如果实现了则调用其 afterpropertiesset()
,然后再检查是否也指定了 init-method()
,如果指定了则通过反射机制调用指定的 init-method()
。
我们也可以发现@postconstruct会在实现 initializingbean 接口的afterpropertiesset()方法之前执行
spring的事件机制
基础概念
spring的事件驱动模型由三部分组成
- 事件:
applicationevent
,继承自jdk的eventobject
,所有事件都要继承它,也就是被观察者 - 事件发布者:
applicationeventpublisher
及applicationeventmulticaster
接口,使用这个接口,就可以发布事件了 - 事件监听者:
applicationlistener
,继承jdk的eventlistener
,所有监听者都继承它,也就是我们所说的观察者,当然我们也可以使用注解@eventlistener
,效果是一样的
事件
在spring框架中,默认对applicationevent事件提供了如下支持:
- contextstartedevent:applicationcontext启动后触发的事件
- contextstoppedevent:applicationcontext停止后触发的事件
- contextrefreshedevent: applicationcontext初始化或刷新完成后触发的事件 ;(容器初始化完成后调用,所以我们可以利用这个事件做一些初始化操作)
- contextclosedevent:applicationcontext关闭后触发的事件;(如 web 容器关闭时自动会触发spring容器的关闭,如果是普通 java 应用,需要调用ctx.registershutdownhook();注册虚拟机关闭时的钩子才行)
构造一个类继承applicationevent
public class testevent extends applicationevent { private string message; public testevent(object source) { super(source); } public void getmessage() { system.out.println(message); } public void setmessage(string message) { this.message = message; } }
创建事件监听者
有两种方法可以创建监听者,一种是直接实现applicationlistener的接口,一种是使用注解 @eventlistener
, 注解是添加在监听方法上的 ,下面的例子是直接实现的接口
@component public class applicationlistenertest implements applicationlistener<testevent> { @override public void onapplicationevent(testevent testevent) { testevent.getmessage(); } }
事件发布
对于事件发布,代表者是 applicationeventpublisher
和 applicationeventmulticaster
,applicationcontext接口继承了applicationeventpublisher,并在abstractapplicationcontext实现了具体代码,实际执行是委托给applicationeventmulticaster(可以认为是多播)
下面是一个事件发布者的测试实例:
@runwith(springrunner.class) @springboottest public class eventtest { @autowired private applicationcontext applicationcontext; @test public void publishtest() { testevent testevent = new testevent(""); testevent.setmessage("hello world"); applicationcontext.publishevent(testevent); } }
利用contextrefreshedevent事件进行初始化操作
利用 contextrefreshedevent
事件进行初始化,该事件是 applicationcontext
初始化完成后调用的事件,所以我们可以利用这个事件,对应实现一个 监听器 ,在其 onapplicationevent()
方法里初始化操作
@component public class applicationlistenertest implements applicationlistener<contextrefreshedevent> { @override public void onapplicationevent(contextrefreshedevent event) { system.out.println("容器刷新完成后,我被调用了.."); } }