• SpringBoot入口深入


    1.run()

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

    SpringApplication.run(Application.class, args);
    
    • 1

    看看方法逻辑

    	/**
    	 * 运行 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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

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

    1.1 程序运行监听器 SpringApplicationRunListeners

    关注语句

    SpringApplicationRunListeners listeners = getRunListeners(args);
    
    • 1

    这里调用了 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));
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    属于 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);
    
    • 1

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

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

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

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

    但是好像看完还是不知道这些个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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    看到这个就很清楚了,特别是选项参数。像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); }
    		...
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

    package org.springframework.core.env;
    
    public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {
        public SimpleCommandLinePropertySource(String... args) {
            super((new SimpleCommandLineArgsParser()).parse(args));
        }
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    SimpleCommandLinePropertySource (简单命令行属性源)用的是它的父类CommandLinePropertySource的构造器初始化的,并且创建了一个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; // 返回
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    也就是说 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);
        }
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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);
        }
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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 副本
        }
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

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

    启动加载顺序

    入口

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

    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();
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

  • 相关阅读:
    Django和Mysql数据库
    cnn训练自己数据集
    SpringBoot原理篇
    【Linux】线程同步和互斥
    文件操作安全之-文件读取原理篇
    基于sklearn实现LDA主题模型(附实战案例)
    19-Redis及Redis集群的介绍、缓存击穿、缓存雪崩、缓存穿透及相应的解决方案
    3分钟学会设计模式 -- 单例模式
    网页js实现的各种3D树形结构模型
    计算机组成与设计硬软件接口学习2
  • 原文地址:https://blog.csdn.net/qq_31856061/article/details/127072840