我们有这样的需求,当spring初始化完成后,我们要做些业务上的初始化,比如说开任务等。

一、通过查看spring启动代码,我们发现了spring会通过Listener的方式,在容器初始化完成后会发送一个Event,实现了Listener就可以在spring初始化完成后拿到spring上下文,进行我们的业务操作。

二、实操

我们采用的方式就是实现ApplicationListener接口,泛型使用ContextRefreshedEvent,即在applicationContext完成refresh的时候接收事件。

package com;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * 应用启动成功后开始启动消费者线程
 * @author jxk
 */
@Configuration
public class ApplicationStartup implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    JdbcTemplate template;
    
    private static final Logger LOGGER =
LoggerFactory.getLogger(ApplicationStartup.class);

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        //获取spring上下文
        ApplicationContext context = contextRefreshedEvent.getApplicationContext();
        LOGGER.info("开始做一些事情");
        JdbcTemplate template1 = context.getBean(JdbcTemplate.class);
        System.out.println(template1.toString());
        System.out.println(template.toString());
    }

}

以下开始稍微的多思考一下。

1.spring究竟是在什么时候发出的event事件。

2.这里的listenter会不会重复触发

3.如果我有多个listener怎么办,是不是有什么方式可以指定多个listener的顺序

下面我来记录我的查找历程。

1. 源码使用springboot 2.2.0版本,从 SpringApplication.run(Config.class,args) 开始。查询调用栈整理如下:

SpringApplication.java
//开始run
(new SpringApplication(primarySources)).run(args);
//方法里关键代码,准备环境,准备工厂,准备context,->刷新context
this.refreshContext(context);
//去刷新
this.refresh(context);
//去刷新
((AbstractApplicationContext)applicationContext).refresh();

AbstractApplicationContext.java
//方法里面关键代码,bean后置工厂处理器,后置处理器,onRefresh,注册listenter,->完成刷新
this.finishRefresh();
//发布event
this.publishEvent((ApplicationEvent)(new ContextRefreshedEvent(this)));

在publishEvent()方法中我们也可以看到spring还会向上找parent的,如果有parent,它会发送parent事件

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        Assert.notNull(event, "Event must not be null");
        Object applicationEvent;
        if (event instanceof ApplicationEvent) {
            applicationEvent = (ApplicationEvent)event;
        } else {
            applicationEvent = new PayloadApplicationEvent(this, event);
            if (eventType == null) {
                eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
            }
        }

        if (this.earlyApplicationEvents != null) {
            this.earlyApplicationEvents.add(applicationEvent);
        } else {
            this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);
        }

        if (this.parent != null) {
            if (this.parent instanceof AbstractApplicationContext) {
                ((AbstractApplicationContext)this.parent).publishEvent(event, eventType);
            } else {
                this.parent.publishEvent(event);
            }
        }

    }

2.如上我们可以知道,重复触发理论上是可行的,只要我加载多个applicationContext就可以完成重复触发,或者说,refresh(context) 就会触发。做一个简单的示例,再加载完成context后,我手动在加载一次,看下触发情况,改动只需要一个main方法。

@EnableAutoConfiguration
@SpringBootApplication(scanBasePackages = {"com"})
public class Main {
    private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationStartup.class);
    public static void main(String[] args) {
        LOGGER.info("let's begin");
        //spring初始化
        ApplicationContext context =SpringApplication.run( Config.class, args);
        //手动再来一次
        ApplicationContext contexts = new AnnotationConfigApplicationContext();
        //注册一下
        ((AnnotationConfigApplicationContext) contexts).register(Config.class);
        //refresh一次
        ((AnnotationConfigApplicationContext) contexts).refresh();
        LOGGER.info( "完成");
    }

}

我们来看看打印情况

spring 加载了那些xml spring加载完后执行某个方法_初始化

ok,事实验证了,那么问题来了,我只想初始化一次,多少次加载,我都只想加载一次。如何做到呢。

方案一,使用类似乐观锁的方案,锁上一次就再不触发,改造的是自定义ApplicationListener接口

@Configuration
public class ApplicationStartup implements ApplicationListener<ContextRefreshedEvent> {
    private volatile AtomicBoolean isInit=new AtomicBoolean(false);
    
    private static final Logger LOGGER =
LoggerFactory.getLogger(ApplicationStartup.class);

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        //防止重复触发,使用原子操作,将尝试将isInit的值 从false->true
        if(!isInit.compareAndSet(false,true)) {
            return;
        }
        //获取spring上下文
        ApplicationContext context = contextRefreshedEvent.getApplicationContext();
        LOGGER.info("开始做一些事情");

        //代码完善处,如果出现异常,要将isInit 改为false,否则都触发不了了。

    }

}

这个方案很简单,也是存在问题的,比如我要做的事情是有关于第二次初始化涉及到的东西,这里在第一次执行完就不会执行,达不到我们的目标。防止重复触发之前context instanceof AnnotationConfigApplicationContext,确定是自己想要的context,然后在操作更好。

3.既然是监听,一定有顺序的,如果想要实现顺序处理,有处理顺序的要求,就要使用SmartApplicationListener 类了。

@Configuration
public class ApplicationStartup implements SmartApplicationListener {

    private volatile AtomicBoolean isInit=new AtomicBoolean(false);
    
    private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationStartup.class);
    
    //我只想要ContextRefreshedEvent,其他event不要
    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
        return aClass==ContextRefreshedEvent.class;
    }

    //值越小,就先触发
    @Override
    public int getOrder() {
        return 0;
    }

    //准备接受所有来源的的event
    @Override
    public boolean supportsSourceType(Class<?> sourceType) {
        return true;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        ApplicationContext context = null;
        ContextRefreshedEvent event = (ContextRefreshedEvent)applicationEvent;
        context = event.getApplicationContext();
        
        //不是我的context不要
        if (context==null||context instanceof AnnotationConfigApplicationContext){
            return;
        }
        //防止重复触发,使用原子操作,将尝试将isInit的值 从false->true
        if(!isInit.compareAndSet(false,true)) {
            return;
        }

        LOGGER.info("开始做一些事情");
        //代码完善处,如果出现异常,要将isInit 改为false,否则都触发不了了。

    }
}

题外,其他类型的event事件,以及自定义event事件,使用后整理发上来。