引言
我们在使用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")); } }
配置参数,然后执行启动类
打印结果
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); } }
配置参数启动
打印结果
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()
方法里被执行