CommandLineRunner与ApplicationRunner接口的使用及源码解析

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: CommandLineRunner与ApplicationRunner接口的使用及源码解析

引言

我们在使用SpringBoot搭建项目的时候,如果希望在项目启动完成之前,能够初始化一些操作,针对这种需求,可以考虑实现如下两个接口(任一个都可以)

org.springframework.boot.CommandLineRunner
org.springframework.boot.ApplicationRunner

CommandLineRunner、ApplicationRunner 接口是在容器启动成功后的最后一步回调(类似开机自启动)。

CommandLineRunner接口

/**
*Interface used to indicate that a bean should <em>run</em> 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 0rdered}* interface or {@link Order @0rder} annotation.* <p>
*If you need access to {@link ApplicationArguments} instead of the raw String array* consider using {@link ApplicationRunner}.
@author Dave Syer
* @see ApplicationRunner
public interface CommandLineRunner {
/**
* Callback used to run the bean.
* @param args incoming main method arguments* @throws Exception on error*/
void run(String... args) throws Exception;

官方doc:

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

接口被用作将其加入spring容器中时执行其run方法。多个CommandLineRunner可以被同时执行在同一个spring上下文中并且执行顺序是以order注解的参数顺序一致。

If you need access to ApplicationArguments instead of the raw String array consider using ApplicationRunner.

如果你需要访问ApplicationArguments去替换掉字符串数组,可以考虑使用ApplicationRunner类。

实例demo

定义一个ServerStartedReport实现CommandLineRunner,并纳入到srping容器中进行处理

import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Order(2)
@Component
public class ServerStartedReport implements CommandLineRunner{
    @Override
    public void run(String... args) throws Exception {
        System.out.println("===========ServerStartedReport启动====="+ LocalDateTime.now());
    }
}

定义一个ServerSuccessReport实现CommandLineRunner,并纳入到spring容器处理

import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Order(1)
@Component
public class ServerSuccessReport implements CommandLineRunner{
    @Override
    public void run(String... args) throws Exception {
        System.out.println("=====应用已经成功启动====="+ Arrays.asList(args));
    }
}

启动类测试,也可以直接在spring容器访问该值,

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context =SpringApplication.run(Application.class,args);
        ApplicationArguments applicationArguments = context.getBean(ApplicationArguments.class);
        System.out.println("============");
        System.out.println("name="+applicationArguments.getOptionNames());
        System.out.println("values===="+applicationArguments.getOptionValues("developer.name"));
    }
}

配置参数,然后执行启动类

df1ac98a37f188663165ad16efc2a18.png

打印结果

2017-07-24 11:07:03.560 INFO  26107 main] com.zhihao.miao.Application Starting Application  
2017-07-24 11:07:03.566 INFO 26107  main  com.zhihao.miao.Application :No active profile set  
2017-07-24 11:07:03.631 INFO 26107  main]s.c.a.AnnotationConfigApplicationContext:Refreshing org.spring 
2017-07-24 11:07:04.288 TNE0 26107  mainl o.s.i.e.a.AnnotationMBeanExporter :Registering beans for  
=----应用已经成功启动=---=[aaa,bbbb]
===ServerStartedReport启动==--=2017-07-24T11:07:04.320
2a17-a7-24 11:a7:04.335.  TNEO 26107  main  hihan miao.Application  :Started Application i  
2017-07-24 11:07:04.336 INFO 26107  Thread-2]s.c.a.AnnotationConfigApplicationContext:Closing org.springfra 
2017-07-24 11:07:04.337 INFO 26107  --- [ Thread-2] o.s.j.e.a.AnnotationMBeanExporter :Unregistering JMX-exp  
Process finished with exit code 0 

ApplicationRunner接口

/**
*Interface used to indicate that a bean should <em>run</em> 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 0rder @0rder} annotation.
* @author Phillip Webb* @since
1.3.0
* @see CommandLineRunner
public interface ApplicationRunner {
/**
* Callback used to run the bean.
* @param args incoming application arguments* @throws Exception on error*/
void run(ApplicationArguments args) throws Exception;
} 

发现二者的官方javadoc一样,区别在于接收的参数不一样。CommandLineRunner的参数是最原始的参数,没有做任何处理。ApplicationRunner的参数是ApplicationArguments,是对原始参数做了进一步的封装。

ApplicationArguments是对参数(main方法)做了进一步的处理,可以解析--name=value的,我们就可以通过name来获取value(而CommandLineRunner只是获取--name=value)

9/**
* Provides access to the arguments that were used to run a {alink SpringApplication}.*
@author Phillip Webb@since 1.3.0*/
public interface ApplicationArguments {
/**
*Return the raw unprocessed arquments that were passed to the application* @return the arguments*/
String[] getSourceArgs();
/**
*Return then names of all option arauments. For example. if the arauments were*"--foo=bar-debug" would return the values {@code ["foo","debuq"1}.* @return the option names or an empty set*/
Set<String> get0ptionNames();
/**
*Return whether the set of ontion arauments parsed from the arauments contains an* option with the given name.* @param name the name to check
*@return {@code true} if the arguments contain an option with the given name*/
boolean contains0ption(String name);
/**
*Return the collection of values associated with the arguments option having the* given name.* <ul>
*<li>if the option is present and has no argument (e.g.: "_-foo"), return an empty* collection ({@code [1})</li>
*<li>if the option is present and has a single value (e.g."--foo=bar"), return a*collection having one element ({@code ["bar"1})</li>
*<li>if the option is present and has multiple values (e.g."_-foo=bar --foo=baz"),* return a collection having elements for each value ({@code ["bar", "baz"]})</li>*<li>if the option is not present, return {@code null}</li>* </ul>
* @param name the name of the option
*@return a list of option values for the given name*/
List<Strina> aetOntionValues(Strina name):
/**
*Return the collection of non-option arguments parsed.*@return the non-option arguments or an empty list*/
List<String> getNon0ptionArgs();
}

可以接收--foo=bar这样的参数。

  • getOptionNames()方法可以得到foo这样的key的集合。
  • getOptionValues(String name)方法可以得到bar这样的集合的value。

实例demo

定义MyApplicationRunner类继承ApplicationRunner接口,

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
public class MyApplicationRunner implements ApplicationRunner{
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("===MyApplicationRunner==="+ Arrays.asList(args.getSourceArgs()));
        System.out.println("===getOptionNames========"+args.getOptionNames());
        System.out.println("===getOptionValues======="+args.getOptionValues("foo"));
        System.out.println("==getOptionValues========"+args.getOptionValues("developer.name"));
    }
}

启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

配置参数启动

dde1acdab5a4486108a6f9f597f0e8f.png

打印结果

MyApplicationRunner==[--foo=bar,--developer.name=zhihao.miao]=get0ptionNames========[foo,developer.name]===get0ptionValues=======[bar]
=get0ptionValues==-=-===[zhihao.miao]
2017-07-24 14:06:24.885 INF0 26854 --- [  main] com.zhihao.miao.Application :Started Application in 1.48  
name=[foo,developer.name] values=--=[zhihao.miao]
2017-07-24 14:06:24.887 INFO 26854 --- [  Thread-2]s.c.a.AnnotationConfigApplicationContext:Closing orq.springframework 
2017-07-24 14:06:24.888 INFO 26854 --- [  Thread-2] o.s.j.e.a.AnnotationMBeanExporter Unregistering JMX-exposed b 
Disconnected from the target VM,address: '127.0.0.1:53255', transport: 'socket' 

总结

用户使用CommandLineRunner或者ApplicationRunner接口均可实现应用启动初始化某些功能的需求,如果希望对参数有更多的操作,则可以选择实现ApplicationRunner接口。

扩展阅读

CommandLineRunner、ApplicationRunner执行流程源码分析

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

SpringApplication.run(args)

public static ConfigurableApplicationContext run(Object source, String... args){
return run(new 0bject[]{source},args);}
public static ConfimrableAnnlicationdontext. run (obieet sources,String[] args){
return  (new SpringApplication(sources)).run(args); 具体方法  
}
public ConfigurableApplicationContext run(String... arq3){
StopWatch stopWatch = new StopWatch(); stopWatch.start();
ConfiqurableApplicationContext context = null; FailureAnalyzers analyzers = null; this.configureHeadlessProperty();
SprinqApplicationRunListeners listeners = this.qetRunListeners(arqs); listeners.starting();
对用户在Arguments输入的参数进行封装
try {
ApplicationArquments applicationArquments = new DefaultApplicationArquments(arqs);
ConfiqurableEnvironment environment=this.prepareEnvironment(listeners, applicationArquments);
Banner printedBanner = this.printBanner(environment); context = this.createApplicationContext(); new FailureAnalyzers(context);
this.prepareContext(context, environment,listeners, applicationArguments, printedBanner);
this.refreshContext(context); 更新完ApplicationContext之后,进行操作,run方法的调用 
this.afterRefresh(context,applicationArquments);  就是在这里执行的  
listeners.finished(context,(Throwable)null); stopWatch.stop();
if(this.logStartupInfo){
(new StartupInfoLoqqer(this.mainApplicationClass)).loqStarted(this.qetApplicationLoq(), stopWatch);}
return context;
} catch (Throwable var9){
this.handleRunFailure(context,listeners,(FailureAnalyzers)analyzers, var9); throw new IlleqalStateException(var9);
)

this.afterRefresh(context, applicationArguments)方法

protected void  afterRefresh(ConfigurableApplicationContext context,  ApplicationArguments args)  
this.callRunners(context,args);}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<object> runners = new ArrayList(); 获取所有实现ApplicationRunner接口的类 
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class获取所有实现CommandLineRunner的接口的类 AnnotationAwareOrderComparator.sort(runners); 根据@Order进行排序 Iterator var4 = (new LinkedHashSet(runners)).iterator();
while(var4.hasNext()){
Object runner =var4.next();
if (runner instanceof ApplicationRunner){ 调用ApplicationRunner的run方法 
this.callRunner((ApplicationRunner)runner, args);}
if (runner instanceof CommandLineRunner){ 调用CommandLineRunner的run方法 
this.callRunner((CommandLineRunner)runner,args);
} 

跟踪context.getBeansOfType()方法,具体实现在类DefaultListableBeanFactory中

@Override
public <T> Map<String, T> : getBeansOfType(Class<T> type) throws BeansException {
return getBeansOfType(type,includeNonSingletons: true, allowEagerInit: true);
@Override
public <T> Map<string,  getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit) 
throws BeansException {
根据类型获取beanName
String[] beanNames=getBeanNamesForType(type,includeNonSingletons, allowEagerInit); Map<String, T> result = new LinkedHashMap<~>(beanNames.length); for (String beanName : beanNames){
try {
result.put (beanName, getBean(beanName, type));
catch (BeanCreationException ex){
Throwable rootCause =ex.getMostSpecificCause();
if(rootCause instanceof BeanCurrentlyInCreationException){
BeanCreationException bce =(BeanCreationException)rootCause
if  (isCurrentlyInCreation(bce.qetBeanName())) {  
if(this.logger.isDebugEnabled()){
this.logger.debug("Ignoring match to currently created bean '" + beanName + "': " +
ex.getMessage());
}
onSuppressedException(ex);
// Iqnore: indicates a circular reference when autowiring constructors.// We want to find matches other than the currently created bean itself. continue;}
throw ex;
}
return result;  
@Override
public String[] qetBeanNamesForType(Class<?> type,boolean includeNonSingletons, boolean allowEaqerInit) {
if(!isConfigurationFrozen() Il type == null Il !allowEagerInit){
doGetBeanNamesForType(ResolvableType.forRawClass(type),includeNonSingletons,allowEaqerInit);
retur
Map<Class<?>, String[]> cache =
(includeNonSingletons?this.allBeanNamesByType:this.singletonBeanNamesByType);
String[] resolvedBeanNames = cache.get(type); if (resolvedBeanNames != null){
return resolvedBeanNames
resolvedBeanNames=doGetBeanNamesForType(ResolvableType.forRawClass(type),includeNonSinqletons,  allowEagerInit: true);  
if(ClassUtils.isCacheSafe(type,getBeanClassLoader())) {
cache.put(type, resolvedBeanNames);}
return resolvedBeanName3; 
private String[]  doGetBeanNamesForType(ResolvableType  type,boolean includeNonSingletons, boolean allowEagerInit)  
List<string> result = new ArrayList<~>();
// Check all bean definitions.
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 l1
((mbd.hasBeanClass()//!mbd.isLazyInit()Il isAllowEaqerClassLoadinq())) &&
!requiresEagerInitForType(mbd.getFactoryBeanName()))){
// In case of FactoryBean, match object created by FactoryBean. boolean isFactoryBean = isFactoryBean(beanName, mbd);
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition(); boolean matchFound =
(allowEagerInit ll !isFactoryBean 11
(dbd != null &&!mbd.isLazyInit()) I1 containsSingleton(beanName)) &&
(includeNonSingletons /I
(dbd!= null ?mbd.isSingleton():isSingleton(beanName))) &&
isTypeMatch(beanName,type);
if  (!matchFound ss isFactoryBean){ 
// In case of FactoryBean, try to match FactoryBean instance itself next. beanName = FACTORY BEAN PREFIX + beanName;
matchFound=(includeNonSingletons11mbd.isSingleton()) && isTypeMatch(beanName, type);
(matchFound){对于符合条件的,添加到result中 result.add(beanName);
catch(CannotLoadBeanClassException ex)
catch (BeanDefinitionStoreException ex) .。s]  

总结:通过以上分析可知,实现这两个接口的类,在ApplicationContext.run()方法里被执行


目录
相关文章
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
87 2
|
10天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
10天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
10天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
57 12
|
29天前
|
PyTorch Shell API
Ascend Extension for PyTorch的源码解析
本文介绍了Ascend对PyTorch代码的适配过程,包括源码下载、编译步骤及常见问题,详细解析了torch-npu编译后的文件结构和三种实现昇腾NPU算子调用的方式:通过torch的register方式、定义算子方式和API重定向映射方式。这对于开发者理解和使用Ascend平台上的PyTorch具有重要指导意义。
|
11天前
|
安全 搜索推荐 数据挖掘
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。
|
3月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
87 0
|
3月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
68 0
|
3月前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
73 0

推荐镜像

更多