SpringBoot入口深入

1.run()

run()方法是一个SpringBoot程序的入口

SpringApplication.run(Application.class, args);

看看方法逻辑

	/**
	 * 运行 Spring 应用程序, 创建并刷新一个新的 应用上下文(应用参数环境)(ApplicationContext)
	 * @param args 应用参数 (usually passed from a Java main method)
	 * @return 一个运行中的应用上下文
	 */
	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);
			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;
	}

分步解析一个SpringBoot应用程序是怎么创建出来的

1.1 程序运行监听器 SpringApplicationRunListeners

关注语句

SpringApplicationRunListeners listeners = getRunListeners(args);

这里调用了 getRunListeners()方法,并且把args参数传进去了,这个是SpringApplication的方法,进入看看

	/** 获取运行监听器 */
	private SpringApplicationRunListeners getRunListeners(String[] args) {
		// 创建了一个 Class 数组 [SpringApplication.class, String[].class]
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		// 创建了一个 SpringApplicationRunListeners 对象返回
		return new SpringApplicationRunListeners(logger,
				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
	}

getRunListeners()方法中创建了一个SpringApplicationRunListeners对象返回,创建途中调用了另外一个成员方法getSpringFactoriesInstances(),往下探

	/** 获取 Spring 工厂实例 */
	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// 用Spring 工厂加载器加载属于 SpringApplicationRunListener.class 这个类型的工厂的名称集合
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

属于 SpringApplicationRunListener.class 这个类型的工厂的名称集合 names:
0 = “org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer”
1 = “org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener”
2 = “org.springframework.boot.devtools.restart.RestartScopeInitializer”
3 = “org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer”
4 = “org.springframework.boot.context.ContextIdApplicationContextInitializer”
5 = “org.springframework.boot.context.config.DelegatingApplicationContextInitializer”
6 = “org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer”
7 = “org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer”
就是说属于 SpringApplicationRunListener的从属类有8个,都是一些初始化器监听器

1.2 应用参数 ApplicationArguments

关注语句

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

ApplicationArguments(应用参数)这个对象,看名字它应该是用来封装SpringBoot应用的参数的,
在分析这个对象之前,先来看参数args是什么参数。

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

在主函数中args是一个string数组参数,而我们的run()方法对应的参数是可变string,没毛病

	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

但是好像看完还是不知道这些个String类型的参数是什么参数,有什么作用
那就直接看ApplicationArguments应用参数这个对象

/** 提供用于运行Sping应用程序(SpringApplication)的参数的访问。*/
public interface ApplicationArguments {

	/** 返回传递给应用程序的原始未处理的参数。*/
	String[] getSourceArgs();

	/** 返回所有选项参数的名称。 如参数是 "--foo=bar --debug",返回 ["foo", "debug"]。 */
	Set<String> getOptionNames();

	/** 判断某个选项参数是否存在在选项参数集合里 */
	boolean containsOption(String name);

	/**
	 * 获取指定选项参数的值集合
	 * 如"--foo", 返回 []
	 * 如"--foo=bar", 返回 ["bar"]
	 * 如"--foo=bar --foo=baz",返回 ["bar", "baz"]
	 * 如果指定的选项参数不存在,返回 null
	 */
	List<String> getOptionValues(String name);

	/** 获取非选项参数集合 */
	List<String> getNonOptionArgs();
}

看到这个就很清楚了,特别是选项参数。像jar包的运行命令

  • java -jar xxx.jar
  • java -jar xxx.jar --server.port=8888

这些启动命令行就带有选项参数,所以说args其实就是运行程序的命令行参数。
接下来深入了解一下ApplicationArguments的默认实现DefaultApplicationArguments

package org.springframework.boot;

public class DefaultApplicationArguments implements ApplicationArguments {
	private final Source source; // 源
	private final String[] args; // 原始参数

	public DefaultApplicationArguments(String... args) {
		Assert.notNull(args, "Args must not be null");
		this.source = new Source(args); // 用 args 创建一个源
		this.args = args;
	}
	/** 这个 source 是 DefaultApplicationArguments 的一个静态内部类
	  *	继承了 SimpleCommandLinePropertySource (简单命令行属性源)
	  */
	private static class Source extends SimpleCommandLinePropertySource {
	
		Source(String[] args) { super(args); }
		...
	}
}

这里看到DefaultApplicationArguments 封装了一个Source (源)对象,而Source 对象用的是它父类SimpleCommandLinePropertySource (简单命令行属性源)的构造器,深入!

package org.springframework.core.env;

public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {
    public SimpleCommandLinePropertySource(String... args) {
        super((new SimpleCommandLineArgsParser()).parse(args));
    }
	...
}

SimpleCommandLinePropertySource (简单命令行属性源)用的是它的父类CommandLinePropertySource<T>的构造器初始化的,并且创建了一个SimpleCommandLineArgsParser对象来解析参数,先看看SimpleCommandLineArgsParser和它的parse()方法

package org.springframework.core.env;

class SimpleCommandLineArgsParser {
    SimpleCommandLineArgsParser() {}

    public CommandLineArgs parse(String... args) {
        CommandLineArgs commandLineArgs = new CommandLineArgs(); // CommandLineArgs 对象,用来保存参数名和参数值
        String[] var3 = args;
        int var4 = args.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String arg = var3[var5];
            if (arg.startsWith("--")) { // 解析"--"开头的参数
                String optionText = arg.substring(2); // 取“--”后面的字符串
                String optionValue = null;
                int indexOfEqualsSign = optionText.indexOf(61); // 获取“=”的位置(61对应ASCLL字符为“=”)
                String optionName;
                if (indexOfEqualsSign > -1) { // 有“=”字符
                    optionName = optionText.substring(0, indexOfEqualsSign);   // 取key
                    optionValue = optionText.substring(indexOfEqualsSign + 1); // 取value
                } else { // 无“=”字符
                    optionName = optionText; // 取key
                }
                
                if (optionName.isEmpty()) { // 如果没有选项参数名,报错
                    throw new IllegalArgumentException("Invalid argument syntax: " + arg);
                }

                commandLineArgs.addOptionArg(optionName, optionValue); // 封装到 commandLineArgs 中
            } else {
                commandLineArgs.addNonOptionArg(arg); // 封装空参
            }
        }
        return commandLineArgs; // 返回
    }
}

也就是说 SimpleCommandLineArgsParser 对象对args按一定规则做参数解析,把原本像--optionName=value这样的参数解析成optionNamevalue,再封装到一个类似键值对包装类的 CommandLineArgs 对象返回。
接下来看 SimpleCommandLinePropertySource (简单命令行属性源)的父类 CommandLinePropertySource< T > 用这个 CommandLineArgs 对象初始化时做了啥

package org.springframework.core.env;

public abstract class CommandLinePropertySource<T> extends EnumerablePropertySource<T> {
    ...
    public CommandLinePropertySource(T source) { // CommandLineArgs commandLineArgs → T source
        super("commandLineArgs", source);
    }
	...
}

CommandLinePropertySource< T > 又调用了它的父类 EnumerablePropertySource (有限属性源)做初始化,继续进去。

package org.springframework.core.env;

public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
    public EnumerablePropertySource(String name, T source) { // (“commandLineArgs",commandLineArgs) 
        super(name, source);
    }
	...
}

EnumerablePropertySource (有限属性源)又调用了它的父类 PropertySource< T > (属性源)做初始化,继续

package org.springframework.core.env;

public abstract class PropertySource<T> {
    protected final Log logger;
    protected final String name;
    protected final T source;

    public PropertySource(String name, T source) {
        Assert.hasText(name, "Property source name must contain at least one character");
        Assert.notNull(source, "Property source must not be null");
        this.name = name;     // 属性名字默认为 “commandLineArgs"
        this.source = source; // 保存 commandLineArgs 副本
    }
	...
}

可以看到 PropertySource< T > 就是尽头了,一路初始化就是为了封装选项参数的keyvalue,最后得到所有参数的

结构图:
在这里插入图片描述

启动加载顺序

入口

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

SpringApplication 的初始化过程

	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader; // 这里传进来的资源加载器是 null
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath(); // Web程序类型是 SERVLET
		// 
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

在这里new SpringApplication(primarySources)的时候调用了loadFactoryNames()
加载的工厂名:org.springframework.context.ApplicationContextInitializer
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值