SpringBoot之ApplicationRunner、CommandLineRunner接口 和@Order注解

我们在开发中可能会有这样的情景。需要在容器启动的时候执行一些内容。列如读取配置文件,数据库连接之类的。SpringBoot给我们提供了ApplicationRunner接口来协助我们实现这种需求。该接口执行时机为容器启动完成的时候。

ApplicationRunner接口

具体代码如下:

@Component
@Order(1)
public class TestImplApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("这个是测试ApplicationRunner接口1");

    }
}

如果有多个实现类,而你需要他们按必定顺序执行的话,可以在实现类上加上@Order注解。@Order(value=整数值)。SpringBoot会按照@Order中的value值从小到大依次执行。

@Component
@Order(2)
public class TestImplApplicationRunner2 implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("这个是测试ApplicationRunner接口2");

    }
}

在加上@Order注解后,执行顺序会根据数字从小到大执行

order的规则:

order的值越小,优先级越高
order如果不标注数字,默认最低优先级,由于其默认值是int最大值
该注解等同于实现Ordered接口getOrder方法,并返回数字。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
  /**
  * The order value.
  * <p>Default is {@link Ordered#LOWEST_PRECEDENCE}.
  * @see Ordered#getOrder()
  */
  int value() default Ordered.LOWEST_PRECEDENCE;
}
 

int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
@Aspect
@Component
public class DataSourceAspect implements Ordered {
  @Override
  public int getOrder() {
    return 1;
  }
}

CommandLineRunner 接口

实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求。
为了解决这样的问题,Spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunner 来实现。

Spring Boot应用程序在启动后,会遍历CommandLineRunner接口的实例并运行它们的run方法。也可以利用@Order注解(或者实现Order接口)来规定所有CommandLineRunner实例的运行顺序。

如下我们使用@Order 注解来定义执行顺序。

package org.springboot.sample.runner;

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

/**
 * 服务启动执行
 *
 */
@Component
@Order(value=2)
public class MyStartupRunner1 implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println(">>>>>>>>>>>>>>>服务启动执行,执行加载数据等操作<<<<<<<<<<<<<");
    }

}

package org.springboot.sample.runner;

import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 服务启动执行
 *
 */
@Component
@Order(value=1)
public class MyStartupRunner2 implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println(">>>>>>>>>>>>>>>服务启动执行,执行加载数据等操作 22222222 <<<<<<<<<<<<<");
    }

}

启动程序后,控制台输出结果为:

>>>>>>>>>>>>>>>服务启动执行,执行加载数据等操作 22222222 <<<<<<<<<<<<<
>>>>>>>>>>>>>>>服务启动执行,执行加载数据等操作 11111111 <<<<<<<<<<<<<

根据控制台结果可判断,@Order 注解的执行优先级是按value值从小到大顺序

CommandLineRunner、ApplicationRunner执行流程源码分析

用户只要实现这两个接口,其中的run方法就会在项目启动时候被自动调用,那么究竟是在什么时候调用的呢?

1)SpringApplication.run(args)

     public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

2)跟踪callRunners(ApplicationContext context, ApplicationArguments args)方法

protected void afterRefresh(ConfigurableApplicationContext context,
            ApplicationArguments args) {
        //callRunners(context, args); //老的版本是在这里调用,由1)的源码可以知道这里实现空,在后面的代码直接调用了callRunners
    }

callRunners代码如下:

    private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList<Object>();
        // 1.获取所有实现ApplicationRunner接口的类
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        // 2.获取所有实现CommandLineRunner的类
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        // 3.根据其@Order进行排序
        AnnotationAwareOrderComparator.sort(runners);
        for (Object runner : new LinkedHashSet<Object>(runners)) {
            // 4.调用ApplicationRunner其run方法
            if (runner instanceof ApplicationRunner) {
                callRunner((ApplicationRunner) runner, args);
            }
            // 5.调用CommandLineRunner其run方法
            if (runner instanceof CommandLineRunner) {
                callRunner((CommandLineRunner) runner, args);
            }
        }
    }

3)跟踪context.getBeansOfType()方法
ListableBeanFactory为interface接口,ConfigurableListableBeanFactory接口继承了ListableBeanFactory,ConfigurableListableBeanFactory的具体实目前类DefaultListableBeanFactory中:

    public <T> Map<String, T> getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
            throws BeansException {
        // 根据类型获取beanName
        String[] beanNames = getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
        Map<String, T> result = new LinkedHashMap<String, T>(beanNames.length);
        for (String beanName : beanNames) {
            try {
                result.put(beanName, getBean(beanName, type));
            }
            ...
        }
        return result;
    }

继续追踪到doGetBeanNamesForType方法

    private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
        List<String> result = new ArrayList<String>();
 
        // 检查容器中所有的bean
        for (String beanName : this.beanDefinitionNames) {
            // Only consider bean as eligible if the bean name
            // is not defined as alias for some other bean.
            if (!isAlias(beanName)) {
                try {
                    RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                    // Only check bean definition if it is complete.
                    if (!mbd.isAbstract() && (allowEagerInit ||
                            ((mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading())) &&
                                    !requiresEagerInitForType(mbd.getFactoryBeanName()))) {
                        // In case of FactoryBean, match object created by FactoryBean.
                        boolean isFactoryBean = isFactoryBean(beanName, mbd);
                        BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
                        boolean matchFound =
                                (allowEagerInit || !isFactoryBean ||
                                        (dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) &&
                                (includeNonSingletons ||
                                        (dbd != null ? mbd.isSingleton() : isSingleton(beanName))) &&
                                isTypeMatch(beanName, type);
                        if (!matchFound && isFactoryBean) {
                            // In case of FactoryBean, try to match FactoryBean instance itself next.
                            beanName = FACTORY_BEAN_PREFIX + beanName;
                            matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type);
                        }
                        // 对于符合类型的,添加到result中
                        if (matchFound) {
                            result.add(beanName);
                        }
                    }
                }
                ...
            }
            return StringUtils.toStringArray(result);
        }

ApplicationRunner CommandLineRunner 区别

总结:通过以上分析可知,实现这两个接口的类,在ApplicationContext.run()方法里被执行。执行的时机在listeners.started(context);和 listeners.running(context);之间,即容器启动完成之后,并且ApplicationRunner先于CommandLineRunner执行,虽然两个接口的实现方法一样,但是参数不一样,其他没什么区别。两个参数都可以接收java命令设置的参数及值,如下3.1.1截图。

ApplicatonRunner的实现类需要实现的方法:

@Overridepublic void run(ApplicationArguments args) {}

CommandLineRunner实现类需要实现的方法

@Overridepublic void run(String... args) {}

设置命令行参数:–spring.profile.active=test,

ApplicatonRunner接口的方法参数ApplicationArguments(是个对象)比CommandLineRunner接口的方法参数(是个可以接收多个string的参数)功能更强劲。

ApplicatonRunner接口的方法参数ApplicationArguments既可以获取参数的字符串,也可以直接获取key;CommandLineRunner接口的方法参数只能获取参数的字符串。

SpringBoot之ApplicationRunner、CommandLineRunner接口 和@Order注解

关键日志信息截图:

SpringBoot之ApplicationRunner、CommandLineRunner接口 和@Order注解

ApplicationRunner接口的实现方法比CommandLineRunner接口的实现方法前执行(当然也可以设置@Order的值来决定谁先执行),如下图。

正常执行的顺序截图

SpringBoot之ApplicationRunner、CommandLineRunner接口 和@Order注解

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容