SpringBoot学习(1)-启动

SpringBoot 学习

学习思路

  • run()方法启动流程以及事件
  • 自动配置原理

启动流程介绍

我们从最开始的启动说起

@SpringBootApplication
public class DemoApplication {

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

}

这里可以看见启动类中主要值执行了SpringApplication.run()这个方法,这个也是整个SpringBoot项目
启动的地方,一路跟进去可以发现,执行了如下的代码

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

可以看见先创建了SpringApplication这个对象,之后执行了这个对象的run方法,按照惯例,先看创建对象的过程

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    //这里的 resourceLoade是一个null值
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //判断当前容器是否是一个web应用
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //设置初始化器
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    //设置监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //返回当前程序的主类
    this.mainApplicationClass = deduceMainApplicationClass();
}

初始化器和监听器着这里起到什么作用?

  • 初始化器ApplicationContextInitializer
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

	/**
	 * Initialize the given application context.
	 * @param applicationContext the application to configure
	 */
	void initialize(C applicationContext);

}

ApplicationContextInitializer是一个回调接口,它会在ConfigurableApplicationContext的refresh()方法调用之前被调用,做一些容器的初始化工作。
这一点我们也可以通过SpringApplication的实例run方法的实现代码得到验证①
可以看到接口传入applicationContext对象,可以直接操作IOC容器

那么 ApplicationContextInitializer是怎么加入容器的,可以跟进setInitializers()这个方法

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));


private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
    Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

这里需要注意到的是SpringFactoriesLoader.loadFactoryNames(type, classLoader))这个方法,我们后边会遇到很多,这个也是
Springboot中比较重要的一个方法,看下内部实现

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
			//这里看见这里取了FACTORIES_RESOURCE_LOCATION这个资源文件,而这个常量是
			//public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

这个方法这个总结下来就是,类路径下所有资源文件 META-INF/spring.factories内的信息,那个这个文件到底配置了什么东西

META-INF/spring.factories部分文件内容

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

这里我们看见了ApplicationContextInitializer字眼,正式我们刚才提到的接口,所以 综合起来,就是很久传入的接口类获取到配置文件中
配置的实现类的全类名,获取到全类名之后,很容易就能想到 通过反射,创建接口的实现对象,同时缓存起来,刚有提到,容器刷新之前调用这些
接口

public void setInitializers(
        Collection<? extends ApplicationContextInitializer<?>> initializers) {
    this.initializers = new ArrayList<>();
    this.initializers.addAll(initializers);
}

初始化器除了配置方式之外,也自行给容器中添加初始化器,实现方式可参考:

ApplicationContextInitializer的三种使用方法

  • 监听器ApplicationListener

同样的原理,相信大家也能猜到ApplicationListener的实现原理,那么这里说下Listener的作用
先看下接口:

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 */
	void onApplicationEvent(E event);

}

1.ApplicationContext事件机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext事件处理。

2.如果容器中有一个ApplicationListener Bean,每当ApplicationContext发布ApplicationEvent时,ApplicationListener Bean将自动被触发。这种事件机制都必须需要程序显示的触发。

3.其中spring有一些内置的事件,当完成某种操作时会发出某些事件动作。比如监听ContextRefreshedEvent事件,当所有的bean都初始化完成并被成功装载后会触发该事件,实现ApplicationListener接口可以收到监听动作,然后可以写自己的逻辑。

4.同样事件可以自定义、监听也可以自定义,完全根据自己的业务逻辑来处理。

之所以需要在这个地方注册监听器,是因为在spring启动的一些地方发布事件需要用到,需要优先加载(自己理解,这里需要子啊进行研究)


至此 new SpringApplication()已经分析完了,记下来分析下run()发方法内执行的逻辑,敲重点

这部分是源码文件,如果感觉看着头痛,可以看我第二段精简部分代码

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启动过程中无关紧要的部分,希望同过此部分代码 分析启动的整个流程

public ConfigurableApplicationContext run(String... args) {
    ConfigurableApplicationContext context = null;
    //1开启监听
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
    
    //2准备环境
    ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
    
    //3.创建上下文
    context = createApplicationContext();
    prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
    
    //4.刷新容器
    refreshContext(context);
    afterRefresh(context, applicationArguments);
    listeners.started(context);
    
    //5.启动完成,回调Runners接口
    callRunners(context, applicationArguments);
    listeners.running(context);
    return context;
}

整个过程可以分解为如上5部分,现在一个个分析。

1.开启监听
方法很直接,获取RunListeners,之后开启,相信之前有对SpringFactoriesLoader.loadFactoryNames(type, classLoader))
这个方法的分析的经验,这里对于获取RunListeners的方式不看源码大家都能分析出来,那么这里主要看下,这个接口内部主要做了什么

public interface SpringApplicationRunListener {
    //开始
	void starting();	
	//环境准备完成
	void environmentPrepared(ConfigurableEnvironment environment);
	//上下文准备完成
	void contextPrepared(ConfigurableApplicationContext context);
	//上下文加载完成
	void contextLoaded(ConfigurableApplicationContext context);
	//启动完成
	void started(ConfigurableApplicationContext context);
	//运行
	void running(ConfigurableApplicationContext context);
	//失败
	void failed(ConfigurableApplicationContext context, Throwable exception);

}

可以看到,这个接口就是包含了SpringBoot的整齐启动过程的监听,触发时机最显著的就是获取完成之后的
listeners.starting();
跟进代码会发现,循环调用每一个监听器的start方法

public void starting() {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.starting();
    }
}

再举例prepareContext()方法

private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context);
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}

在准备上下文之后,回调listeners.contextLoaded(context);监听器的方法,因此要监控SpringBoot的启动过程
就可以实现此接口。

2.prepareEnvironment创建环境,还未深入研究 ,此处不做分析

3.准备上下文
此处重点分析 context = createApplicationContext();

可以看到,上下文环境是通过这个方法创建出来的


protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

这段代码的意思很明显,就是根据不同的上下类型去实例化不同的上下文。

前边①的地方提到,在容器刷新之前,会先执行初始化器的接口,就在prepareContext()方法中的

private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    //回调初始化器方法
    applyInitializers(context);
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    // Load the sources
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    listeners.contextLoaded(context);
}
	
		
/**
* 回调初始化器方法
 */		
protected void applyInitializers(ConfigurableApplicationContext context) {
    for (ApplicationContextInitializer initializer : getInitializers()) {
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
    }
}

5.刷新容器

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值