第三章 SpringBoot构造流程源码分析

3.1 SpringApplication的初始化简介

springboot的启动非常简单,只需要一个启动类,运行main方法即可启动应用。

@SpringBootApplication
public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args); // 静态方法
    }
}

SpringApplication.run方法

run方法是SpringApplication中的静态方法,

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	// 将入口类 包装成 一个数组, 意思是我们可以传入多个这样的primarySource
	return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	
	// 这样就看的很明显了, 仅仅2个步骤
	// 第一步: 先创建了一个 SpringApplication 实例, 并且传入了 入口类 (数组类型)
	// 第二步: 调用 SpringApplication实例 的run方法
	return new SpringApplication(primarySources).run(args);
}

那么我们实际上就可以这样写,来启动应用了

@SpringBootApplication(scanBasePackages = "com.zzhua.test")
public class TestApplication {

    public static void main(String[] args) throws IOException {

        // SpringApplication.run(TestApplication.class, args);

		// 这样写的好处是, 我们可以在运行run方法前, 自由的设置属性了,比较灵活, 但没特殊需求的话, 没必要这样做
        SpringApplication springApplication = new SpringApplication();
        springApplication.addPrimarySources(Arrays.asList(TestApplication.class));
        springApplication.run(args);
    }
}

但是,如果一定要这么做呢?springboot还提供了一个SpringApplicationBuilder,所以也可以这样写

@SpringBootApplication
public class TestApplication {

    public static void main(String[] args) throws IOException {

        // SpringApplication.run(TestApplication.class, args);

        new SpringApplicationBuilder()
                .bannerMode(Banner.Mode.OFF)
                .sources(new Class[]{TestApplication.class})
                .run(args)
        ;
}

3.2 SpringApplication实例化流程

new SpringApplication(primarySources).run(args)分为2步骤,本篇只探讨SpringApplication实例化部分,run部分后面再分析。

public SpringApplication(Class<?>... primarySources) {
	this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

	// 默认传入的resourceLoader是null,资源加载器默认使用DefaultResourceLoader
	this.resourceLoader = resourceLoader;
	
	Assert.notNull(primarySources, "PrimarySources must not be null");

	// 对 primarySources 去重
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

	// 从类路径上, 使用判断 是否存在 指定的类, 比如: DispatcherServlet[SERVLET]、DispatcherHandler(REACTIVE)
	// 就是通过ClassUtils.isPresent(xxxClass)加载类, 能加载到就存在, 加载失败就不存在
	this.webApplicationType = WebApplicationType.deduceFromClasspath();

	// 通过SpringFactoriesLoader加载spring.factories文件中, ApplicationContextInitializer全类名所对应配置的初始化器
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	
	// 通过SpringFactoriesLoader加载spring.factories文件中, ApplicationListener全类名所对应配置的初始化器
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

	// 推断主类(第一个main方法所在的类)
	this.mainApplicationClass = deduceMainApplicationClass();
}

deduceFromClasspath

推断应用类型

static WebApplicationType deduceFromClasspath() {
	// 存在"org.springframework.web.reactive.DispatcherHandler"
	// 不存在"org.springframework.web.servlet.DispatcherServlet"
	// 不存在"org.glassfish.jersey.servlet.ServletContainer"
	// 则为: REACTIVE
	if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
			&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
		return WebApplicationType.REACTIVE;
	}

	// "javax.servlet.Servlet",
	// "org.springframework.web.context.ConfigurableWebApplicationContext"
	// 上面2个必须都存在, 才是SERVLET, 否则就不是web应用
	for (String className : SERVLET_INDICATOR_CLASSES) {
		if (!ClassUtils.isPresent(className, null)) {
			return WebApplicationType.NONE;
		}
	}
	return WebApplicationType.SERVLET;
}

获取ApplicationContextInitializer实例

ApplicationContextInitializer接口的实例,用来在执行ConfigurableApplicationContext容器的refresh刷新方法之前,对容器的一些初始化操作。

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

	
	void initialize(C applicationContext);
}

getSpringFactoriesInstances

getSpringFactoriesInstances(ApplicationContextInitializer.class);

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {

	// 第二个参数表示, 使用type类中指定构造参数类型的构造器,来实例化
	// 其实还有第三个参数, 它是可变数组, 所以这里没写, 表示传入的构造参数
	// 这里,显然是获取无参构造器来实例化, 所以也没传入构造参数
	return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {

	ClassLoader classLoader = getClassLoader();
	
	// 使用SpringFactoriesLoader加载spring.factories文件指定类型全类名所对应的配置的名称
	// 具体加载过程见:https://www.processon.com/view/link/61f0c54ee0b34d616d516e16
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	
	// 使用反射创建对应的实例
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	
	// 对这些实例按照@Orser注解或Order接口的值来进行排序,值越小排序越靠前
	AnnotationAwareOrderComparator.sort(instances);
	
	return instances;
}

下面这种创建方法,我们也可以直接拿来用

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

配置ApplicationContextInitializer实例

如果要配置ApplicationContextInitializer实例有3种方式
1- 将实现类写在META-INF/spring.factories文件中

org.springframework.context.ApplicationContextInitializer=\
MyApplicationContextInitializer

2- 通过application.yml或application.properties配置,这是通过默认加载的DelegatingApplicationContextInitializer实现的

context.initializer.classes = MyApplicationContextInitializer

3- 可以用我们前面提到的SpringApplication实例来添加

@SpringBootApplication(scanBasePackages = "com.zzhua.test")
	public class TestApplication {
	
	 public static void main(String[] args) throws IOException {
	
	     SpringApplication.run(TestApplication.class, args);
	
	     SpringApplication springApplication = new SpringApplication();
	     springApplication.addPrimarySources(Arrays.asList(TestApplication.class));
	     
	     // 主动添加
	     springApplication.addInitializers(new MyApplicationContextInitializer);
	     springApplication.run(args);
     }
}

获取ApplicationListener实例

ApplicationListener实例用于监听容器中发布的各种事件,在发布对应的事件时,做相应的处理。
我们也可以手动使用ApplicationContext的publishEvent方法,主动的发布事件。

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	// ApplicationListener接口的实例,作为应用的监听器,当容器发布事件时,
	// 通过回调监听器的onApplicationEvent方法,监听器就能接收到该事件(观察者模式)
	void onApplicationEvent(E event);
}

deduceMainApplicationClass

通过new一个运行时异常,获取栈,找到第一个main方法所在的类就是主类

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

3.3 SpringApplication的定制化修改

3.3.1 基础配置

  1. 使用application.yml配置文件
  2. 手动自己new SpringApplication实例或使用SpringApplicationBuilder来构建实例

3.3.2 配置源配置

如果我们在springboot应用中,仍然想使用xml配置文件,那又该怎么做呢?
在SpringApplication中,有个sources属性,可以传入包名、类名、xml文件

private Set<String> sources = new LinkedHashSet<>();

public Set<Object> getAllSources() {
	Set<Object> allSources = new LinkedHashSet<>();
	if (!CollectionUtils.isEmpty(this.primarySources)) {
		allSources.addAll(this.primarySources);
	}
	if (!CollectionUtils.isEmpty(this.sources)) {
		allSources.addAll(this.sources);
	}
	return Collections.unmodifiableSet(allSources);
}

private void prepareContext(){
	// ...
	Set<Object> sources = getAllSources();
	load(context, sources.toArray(new Object[0]));
	// ...
}

# load方法会调用到 BeanDefinitionLoader的load, 去载入对应的资源
private int load(Object source) {
		Assert.notNull(source, "Source must not be null");
		if (source instanceof Class<?>) {
			return load((Class<?>) source);
		}
		if (source instanceof Resource) {
			return load((Resource) source);
		}
		if (source instanceof Package) {
			return load((Package) source);
		}
		if (source instanceof CharSequence) {
			return load((CharSequence) source);
		}
		throw new IllegalArgumentException("Invalid source type " + source.getClass());
	}

版权声明:本文为qq_16992475原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。