前言
本文是作者写关于Spring源码的第一篇文章,作者水平有限,所有的源码文章仅限用作个人学习记录。文中如有错误欢迎各位留言指正。
run
public ConfigurableApplicationContext run(String... args) {
// 为了计时用的,老版本和新版本不一样
long startTime = System.nanoTime();
// 初始化一个引导器的上下文,这是属于Spring Boot的上下文。后边还有一个Spring的上下文。apach好喜欢context这个东西,证明写框架这个context是真的好用。
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 这是Spring的上下文,在这里定义,在下面进行的初始化
ConfigurableApplicationContext context = null;
// 配置一个系统属性
configureHeadlessProperty();
// 获取配置文件的监听器 重点 也是扩展点,凡是读取配置文件的地方都是扩展点,因为配置在配置文件中的initializer、listener都会在某个阶段被调用
SpringApplicationRunListeners listeners = getRunListeners(args);
// 调用监听器发送启动事件,这里可以通过自定义监听器消费这个事件,处理自己的逻辑
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 解析命令行参数 将其封装成一个ApplicationArguments,这个类的变量name被设置成commandLineArgs字符串,变量source是解析args封装的CommandLineArgs对象。
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 环境预处理
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 配置忽略beanInfo
configureIgnoreBeanInfo(environment);
// 打印banner信息
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
printBanner
private Banner printBanner(ConfigurableEnvironment environment) {
判断banner的模式,一共有三种模式off、console、log,见下图一。默认是console
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
resourceLoader默认是null,构造函数中进行的赋值,函数的对应的参数就是null。
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(null);
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
日志文件打印
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
控制台打印
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
- 图一
Banner print(Environment environment, Class<?> sourceClass, Log logger) {
获取banner 这里面可以解密我们自定义banner的原理
Banner banner = getBanner(environment);
try {
打印信息
logger.info(createStringFromBanner(banner, environment, sourceClass));
}
catch (UnsupportedEncodingException ex) {
logger.warn("Failed to create String for banner", ex);
}
return new PrintedBanner(banner, sourceClass);
}
getBanner
private Banner getBanner(Environment environment) {
Banners banners = new Banners();
添加图片的banner
banners.addIfNotNull(getImageBanner(environment));
添加文本的banner
banners.addIfNotNull(getTextBanner(environment));
是否至少包含一个banner
if (banners.hasAtLeastOneBanner()) {
如果是直接返回banners
return banners;
}
参数其实是null
if (this.fallbackBanner != null) {
return this.fallbackBanner;
}
默认banner 见下面的图一
return DEFAULT_BANNER;
}
是不是很熟悉。
getImageBanner
- 如果配置了spring.banner.image.location属性 直接读取这个属性的资源作为imagebanner
- 如果没有配置上面的属性,直接在classpath下放置一个banner命名,"gif", "jpg", "png" 这些后缀的图片格式也可以被加载称为imagebanner
private Banner getImageBanner(Environment environment) {
获取spring.banner.image.location属性值,是从环境变量中获取的,那我们可以在环境变量中设置这个属性哟
String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
if (StringUtils.hasLength(location)) {
如果有这个属性就用资源加载器加载该资源
Resource resource = this.resourceLoader.getResource(location);
资源不为空 直接转成图片类型的banner
return resource.exists() ? new ImageBanner(resource) : null;
}
for (String ext : IMAGE_EXTENSION) {
可以加载这些后缀的banner图片{ "gif", "jpg", "png" }
Resource resource = this.resourceLoader.getResource("banner." + ext);
if (resource.exists()) {
return new ImageBanner(resource);
}
}
return null;
}
getTextBanner
private Banner getTextBanner(Environment environment) {
或者spring.banner.location这个环境变量属性值,所以这里我们也是可以配置属性来控制加载哪个banner,如果不存在默认值为banner.txt
String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
加载类路径下的spring.banner.location资源或者直接加载banner.txt
Resource resource = this.resourceLoader.getResource(location);
try {
if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) {
存在就生成banner资源
return new ResourceBanner(resource);
}
}
catch (IOException ex) {
// Ignore
}
return null;
}
createStringFromBanner
private String createStringFromBanner(Banner banner, Environment environment, Class<?> mainApplicationClass)
throws UnsupportedEncodingException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
默认的话是SpringBootBanner的printBanner
banner.printBanner(environment, mainApplicationClass, new PrintStream(baos));
String charset = environment.getProperty("spring.banner.charset", "UTF-8");
return baos.toString(charset);
}
printBanner
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {
for (String line : BANNER) {
banner信息保存到输入流
printStream.println(line);
}
拼接版本信息
String version = SpringBootVersion.getVersion();
version = (version != null) ? " (v" + version + ")" : "";
StringBuilder padding = new StringBuilder();
while (padding.length() < STRAP_LINE_SIZE - (version.length() + SPRING_BOOT.length())) {
padding.append(" ");
}
版本信息保存到输入流
printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT, AnsiColor.DEFAULT, padding.toString(),
AnsiStyle.FAINT, version));
换行
printStream.println();
}
OK 今天先到这里吧。
See you next time :)