欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

应用启动数据初始化接口CommandLineRunner和Application详解

程序员文章站 2022-03-15 20:13:56
目录应用启动数据初始化接口commandlinerunner和application详解1 运行时机2 实现接口3 执行顺序4 设置启动参数5 运行效果applicationrunner和comman...

应用启动数据初始化接口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中设置启动参数如下图(生产环境中会自动读取命令行启动参数):

应用启动数据初始化接口CommandLineRunner和Application详解

5 运行效果

在上面的两个runner中,设定了commandlinerunnerinit是第一个,applicationrunnerinit是第二个。启动应用,运行效果如下图:

应用启动数据初始化接口CommandLineRunner和Application详解

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 {
 }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。