对我来说,写博客是一个自我学习,自我提升的一种方式,本文所有的内容也是我查看了好多博主的文章后,在自我探索和验证的过程,我会尽量把所有涉及到的方法都写在本文中,也希望您能在阅读的同时对我提出宝贵意见。

    我们都知道springboot都有一个main方法,main里面调用SpringApplication.run()启动整个spring-boot程序,args就是我们执行启动命令带入的参数,默认以空格隔开

 从main函数说起   

/**
 * com.bicai.channelmanager
 * {@link }
 *
 * @author lydong
 * @see
 * @since 2018/7/10 上午11:48
 */
@MapperScan("com.bicai.channelmanager.**.mapper")
@EnableFeignClients({"com.bicai.channelmanager.server", "com.bicai.common.component.feign"})
@EnableEurekaClient
@SpringBootApplication
public class ChannelManagerBootStrapApplication {
    public static void main(String[] args) {
        SpringApplication.run(ChannelManagerBootStrapApplication.class, args);
    }
}

从run方法点进去看看

/**
	 * Static helper that can be used to run a {@link SpringApplication} from the
	 * specified source using default settings.
	 * @param primarySource the primary source to load
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return the running {@link ApplicationContext}
	 */
	public static ConfigurableApplicationContext run(Class<?> primarySource,
			String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

从这里可以看出,我们传入的class参数被包装成数组,可以发现springboot其实可以同时开启多个应用。执行run方法返回SpringApplication实例。而且这里只有一个方法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);
	}

走到这里,发现这里方法是通过两步来达成的,首先是SpringApplication的构造方法,然后调用其run方法。先从构造方法看,进去,里面是this(null,primarySources)方法,直接进去

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
                //1、资源初始化资源加载器为 null
		this.resourceLoader = resourceLoader;
                //2、验证非null
		Assert.notNull(primarySources, "PrimarySources must not be null");
                //3、初始化主要加载资源类集合并用set去重
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
                //4、推断当前 WEB 应用类型
		this.webApplicationType = deduceWebApplicationType();
                //5、设置应用上下文初始化器(getSpringFactoriesInstances内部通过Thread.currentThread().getContextClassLoader()获取加载器加载)
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
                //6、设置监听器
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
                //7、推断主入口应用类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

如何判断的WEB应用类型的呢

private WebApplicationType deduceWebApplicationType() {
		if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
				&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : WEB_ENVIRONMENT_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
			+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

	private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
			+ "web.reactive.DispatcherHandler";

	private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
			+ "web.servlet.DispatcherServlet";

public enum WebApplicationType {

	/**
	 * The application should not run as a web application and should not start an
	 * embedded web server.
	 */
	NONE,

	/**
	 * The application should run as a servlet-based web application and should start an
	 * embedded servlet web server.
	 */
	SERVLET,

	/**
	 * The application should run as a reactive web application and should start an
	 * embedded reactive web server.
	 */
	REACTIVE

}

isPresent方法里很简单,就是尝试去加载这个类,如果这个类在项目的依赖里,那么会返回true,也就会返回响应web类型,是一个枚举类.在往下看。

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

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

}

用来初始化指定的 Spring 应用上下文,如注册属性资源、激活 Profiles 等,再来看下setInitializers方法

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

就是初始化一个 ApplicationContextInitializer 应用上下文初始化器实例的集合

在来看下getSpringFactoriesInstances方法

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new 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));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

设置应用上下文初始化器主要分以下五个步骤:

1、获取当前线程的上下文类加载器

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

2、获取ApplicationContextInitializer 实例名称并去重

Set<String> names = new LinkedHashSet<>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));

loadFactoryNames 方法相关的源码如下:

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

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

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 ?
                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()) {
                List<String> factoryClassNames = Arrays.asList(
                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                result.addAll((String) entry.getKey(), factoryClassNames);
            }
        }
        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 文件解析并获取 ApplicationContextInitializer 接口的所有配置的类路径名称。

在根据传入的type匹配加载的class

 查看该文件spring-boot-autoconfigure-2.0.3.RELEASE.jar,/META-INF/spring.factories 的初始化器相关配置内容如下:

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

3、根据以上的类路径创建初始化器实例列表

List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);

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<?> 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;
}

4、初始化器实例列表排序

AnnotationAwareOrderComparator.sort(instances);

 设置完初始化器之后,设置监听器,可以看下这个类ApplicationListener

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

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

}

看到这里可知,ApplicationListener继承了JDK的java.util.EventListener接口,实现了观察者模式,在Spring 3.0时,ApplicationListener可以通用地声明事件类型是它感兴趣的。当用Spring ApplicationContext注册时,事件将相应地过滤*,并调用侦听器以匹配事件

设置监听器和设置初始化器调用的方法是一样的,只是传入的类型不一样,设置监听器的接口类型为:

getSpringFactoriesInstances,对应的 spring-boot-autoconfigure-2.0.3.RELEASE.jar,/META-INF/spring.factories 文件配置内容请见下方。

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

 可以看出目前只有一个 BackgroundPreinitializer 监听器。

然后推断主入口应用类

this.mainApplicationClass = deduceMainApplicationClass();去看源码

private Class<?> deduceMainApplicationClass() {
		try {
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}

这个方式很特别,我是第一次看到,是构造一个运行时异常,在遍历异常栈中的方法名,找到方法名匹配main方法的栈帧,然后返回该类

到这,SpringApplication的构造方法的初始化流程就执行完毕,之后执行run方法,内容很多,下一章在总结。