springboot2.0.6 启动过程(一)

springboot启动
  • 先从主类的run方法开始,事例代码如下
 public static void main(String[] args) {
        SpringApplication.run(PromoteAdminApplication.class, args);
        logger.info("PromoteAdminApplication is success!");
    }
  • 跟进run方法最终可以看到
    /**
	 * Static helper that can be used to run a {@link SpringApplication} from the
	 * specified sources using default settings and user supplied arguments.
	 * @param primarySources the primary sources to load
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return the running {@link ApplicationContext}
	 */
	public static ConfigurableApplicationContext run(Class<?>[] primarySources,
			String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

我们根据注释看到,代码是在创建一个spring的应用。继续跟进new SpringApplication(primarySources),run方法后面在看,先看在new的时候程序都做了什么。

     /**
	 * Create a new {@link SpringApplication} instance. The application context will load
	 * beans from the specified primary sources (see {@link SpringApplication class-level}
	 * documentation for details. The instance can be customized before calling
	 * {@link #run(String...)}.
	 * @param resourceLoader the resource loader to use
	 * @param primarySources the primary bean sources
	 * @see #run(Class, String[])
	 * @see #setSources(Set)
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
                 //设置资源加载类ResourceLoader,传的都是null
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
                //设置主类,就是我们最开始启动类
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
                //设置应用类型 根据加载的目标类是否存在来判断
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
                // **此处是spring自己的spi机制,在META-INF/spring.factories加载class,这也是自动配置与去xml的基础,这里加载的**
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
                // 通过spi机制加载ApplicationListener类型的class
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 
               // 设置运行的主类,并保存在spring应用类的属性内
		this.mainApplicationClass = deduceMainApplicationClass();
	}

可以看到 SpringApplication创建时,做了很多初始化的工作
我们先看下Spring如何使用spi机制

  • 加载对应属性文件并初始化加载的class
	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
                //实例化对应clas
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
                //排序
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}
  • 如何加载
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
		try {
                         //指定加载路径 FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
                         
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
                // 创建map 装载属性文件内容的一个容器()
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
                //加载URL下的文件
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					List<String> factoryClassNames = Arrays.asList(
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
					result.addAll((String) entry.getKey(), factoryClassNames);
				}
			}
            // 根据线程的类加载器为key,缓存加载的属性文件的内容
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

这段代码目的就是加载好spring.factories内的class名称,
但是此时springboot在加载的时候 加载的是ApplicationContextInitializer类型的class名称

	public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
	}

加载完成后 springboot就开始实例化这些class

  • 如何实例化
    主要是利用反射初始化对象
	private <T> List<T> createSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
			Set<String> names) {
		List<T> instances = new ArrayList<>(names.size());
		for (String name : names) {
			try {
                        //根据名字反射获取class对象
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				Constructor<?> constructor = instanceClass
						.getDeclaredConstructor(parameterTypes);
                        //实例化对象
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException(
						"Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}

还记得最开始的SpringApplication 构造函数初始化的步骤吗
setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class));
这里面可以看下里面的源码

  • 这里面可以看到 将ApplicationContextInitializer实例存到list内,注入到spring的对象属性内,作用就是后面spring启动过程中使用此方法进行初始化配置。
    /**
	 * Sets the {@link ApplicationContextInitializer} that will be applied to the Spring
	 * {@link ApplicationContext}.
	 * @param initializers the initializers to set
	 */
	public void setInitializers(
			Collection<? extends ApplicationContextInitializer<?>> initializers) {
		this.initializers = new ArrayList<>();
		this.initializers.addAll(initializers);
	}
  • 同理先存储ApplicationListener类型class实例,后面使用
    /**
	 * Sets the {@link ApplicationListener}s that will be applied to the SpringApplication
	 * and registered with the {@link ApplicationContext}.
	 * @param listeners the listeners to set
	 */
	public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
		this.listeners = new ArrayList<>();
		this.listeners.addAll(listeners);
	}

这里看到此时spring已经把需要的监听加载完成,只要等待对应事件的发布就ok了
这里只是SpringApplication的构造过程而已,然后调用run方法。这里才是真正的启动spring应用的方法。

  • 主要步骤如下 1. 配置属性 > 2. 获取监听器,发布应用开始启动事件 > 3. 初始化输入参数 > 4. 配置环境,输出 banner > 5. 创建上下文 > 6. 预处理上下文 > 7. 刷新上下文 > 8. 上下文已刷新启动完成 发布启动完成事件 > 9. 调用CommandLineRunners与ApplicationRunner的run方法 > 10. 发布应用上下文就绪事件

refreshContext方法就是spring容器的主要方法

	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
            //1. 配置属性 主要设置 java.awt.headless属性,目的就是在缺少显示屏时也可以启动
	 	configureHeadlessProperty();
        
           // 2. 获取监听器,发布应用开始启动事件
           //获取SpringApplicationRunListeners,内部只有一个EventPublishingRunListener 这里会将META-INF/spring.factories内的信息全部加载到map内
	 	SpringApplicationRunListeners listeners = getRunListeners(args);
          //启动监听
		listeners.starting();
		try {
           //3. 初始化输入参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
            //3. 初始化输入参数        
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			configureIgnoreBeanInfo(environment);
            4. 配置环境,输出 banner
			Banner printedBanner = printBanner(environment);
            //5. 创建上下文 
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);   
           // 6. 预处理上下文         
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
           //7. 刷新上下文
			refreshContext(context);
           //刷新context后执行,空方法
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
            // 8.上下文已刷新启动完成 发布启动完成事件
			listeners.started(context);
            //9. 调用CommandLineRunners与ApplicationRunner的run方法
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
           //10.发布应用上下文就绪事件
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值