应用启动数据初始化接口CommandLineRunner和Application详解
应用启动数据初始化接口commandlinerunner和application详解
在springboot项目中创建组件类实现commandlinerunner或applicationrunner接口可实现在应用启动之后及时进行一些初始化操作,如缓存预热、索引重建等等类似一些数据初始化操作。
两个接口功能相同,都有个run方法需要重写,只是实现方法的参数不同。
commandlinerunner接收原始的命令行启动参数,applicationrunner则将启动参数对象化。
1 运行时机
两个接口都是在springboot应用初始化好上下文之后运行,所以在运行过程中,可以使用上下文中的所有信息,例如一些bean等等。在框架springapplication类源码的run方法中,可查看runner的调用时机callrunners,如下:
/** * run the spring application, creating and refreshing a new * {@link applicationcontext}. * @param args the application arguments (usually passed from a java main method) * @return a running {@link applicationcontext} */ public configurableapplicationcontext run(string... args) { stopwatch stopwatch = new stopwatch(); stopwatch.start(); configurableapplicationcontext context = null; collection<springbootexceptionreporter> exceptionreporters = new arraylist<>(); configureheadlessproperty(); springapplicationrunlisteners listeners = getrunlisteners(args); listeners.starting(); try { applicationarguments applicationarguments = new defaultapplicationarguments(args); configurableenvironment environment = prepareenvironment(listeners, applicationarguments); configureignorebeaninfo(environment); banner printedbanner = printbanner(environment); context = createapplicationcontext(); exceptionreporters = getspringfactoriesinstances(springbootexceptionreporter.class, new class[] { configurableapplicationcontext.class }, context); preparecontext(context, environment, listeners, applicationarguments, printedbanner); refreshcontext(context); afterrefresh(context, applicationarguments); stopwatch.stop(); if (this.logstartupinfo) { new startupinfologger(this.mainapplicationclass).logstarted(getapplicationlog(), stopwatch); } listeners.started(context); //调用runner,执行初始化操作 callrunners(context, applicationarguments); } catch (throwable ex) { handlerunfailure(context, ex, exceptionreporters, listeners); throw new illegalstateexception(ex); } try { listeners.running(context); } catch (throwable ex) { handlerunfailure(context, ex, exceptionreporters, null); throw new illegalstateexception(ex); } return context; }
2 实现接口
2.1 commandlinerunner
简单实现如下,打印启动参数信息:
@order(1) @component public class commandlinerunnerinit implements commandlinerunner { private logger logger = loggerfactory.getlogger(this.getclass()); private final string log_prefix = ">>>>>>>>>>commandlinerunner >>>>>>>>>> "; @override public void run(string... args) throws exception { try { this.logger.error("{} args:{}", log_prefix, stringutils.join(args, ",")); } catch (exception e) { logger.error("commandlinerunnerinit run failed", e); } } }
2.2 applicationrunner
简单实现如下,打印启动参数信息,并调用bean的方法(查询用户数量):
@order(2) @component public class applicationrunnerinit implements applicationrunner { private logger logger = loggerfactory.getlogger(this.getclass()); private final string log_prefix = ">>>>>>>>>>applicationrunner >>>>>>>>>> "; private final userrepository userrepository; public applicationrunnerinit(userrepository userrepository) { this.userrepository = userrepository; } @override public void run(applicationarguments args) throws exception { try { this.logger.error("{} args:{}", log_prefix, jsonobject.tojsonstring(args)); for (string optionname : args.getoptionnames()) { this.logger.error("{} argname:{} argvalue:{}", log_prefix, optionname, args.getoptionvalues(optionname)); } this.logger.error("{} user count:{}", log_prefix, this.userrepository.count()); } catch (exception e) { logger.error("commandlinerunnerinit run failed", e); } } }
3 执行顺序
如果实现了多个runner,默认会按照添加顺序先执行applicationrunner的实现再执行commandlinerunner的实现,如果多个runner之间的初始化逻辑有先后顺序,可在实现类添加@order注解设置执行顺序,可在源码springapplication类的callrunners方法中查看,如下:
private void callrunners(applicationcontext context, applicationarguments args) { list<object> runners = new arraylist<>(); //先添加的applicationrunner实现 runners.addall(context.getbeansoftype(applicationrunner.class).values()); //再添加的commandlinerunner实现 runners.addall(context.getbeansoftype(commandlinerunner.class).values()); //如果设置了顺序,则按设定顺序重新排序 annotationawareordercomparator.sort(runners); for (object runner : new linkedhashset<>(runners)) { if (runner instanceof applicationrunner) { callrunner((applicationrunner) runner, args); } if (runner instanceof commandlinerunner) { callrunner((commandlinerunner) runner, args); } } }
4 设置启动参数
为了便于对比效果,在idea中设置启动参数如下图(生产环境中会自动读取命令行启动参数):
5 运行效果
在上面的两个runner中,设定了commandlinerunnerinit是第一个,applicationrunnerinit是第二个。启动应用,运行效果如下图:
applicationrunner和commandlinerunner用法区别
业务场景:
应用服务启动时,加载一些数据和执行一些应用的初始化动作。如:删除临时文件,清除缓存信息,读取配置文件信息,数据库连接等。
1、springboot提供了commandlinerunner和applicationrunner接口。当接口有多个实现类时,提供了@order注解实现自定义执行顺序,也可以实现ordered接口来自定义顺序。
注意:数字越小,优先级越高,也就是@order(1)注解的类会在@order(2)注解的类之前执行。
两者的区别在于:
applicationrunner中run方法的参数为applicationarguments,而commandlinerunner接口中run方法的参数为string数组。想要更详细地获取命令行参数,那就使用applicationrunner接口
applicationrunner
@component @order(value = 10) public class agentapplicationrun2 implements applicationrunner { @override public void run(applicationarguments applicationarguments) throws exception { } }
commandlinerunner
@component @order(value = 11) public class agentapplicationrun implements commandlinerunner { @override public void run(string... strings) throws exception { } }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。