SpringBoot源码解析三部曲(二)——SpringApplication实例化

关联文章:
SpringBoot源码解析三部曲(一)——自动配置
SpringBoot源码解析三部曲(三)——运行流程.

1、SpringApplication实例化简介

常见启动入口类示例:

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SpringBootDemoApplication {

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

}

启动入口类中,主要通过SpringApplication的静态方法-run方法进行SpringApplication类的实例化操作,然后再使用实例化对象调用另一个run方法来完成整个项目的初始化和启动。

SpringApplication源码中run方法:

// 参数primarySource为加载的主要资源类,通常就是Spring Boot的入口类
// 参数args为传递给应用程序的参数信息
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

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

2、SpringApplication实例化流程

核心流程如图所示:

实例化流程
从图中可以看出,在SpringApplication对象实例化的过程中,主要做了3件事:参数赋值给成员变量、应用类型及方法推断、ApplicationContext相关内容加载及实例化。源码如下:

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

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    	// 赋值成员变量resourceLoader
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
    	// 赋值成员变量primarySources
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    	// 推断Web应用类型
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
    	// 加载并初始化ApplicationContextInitializer及相关实现类
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    	// 加载并初始化ApplicationListener及相关实现类
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    	// 推断main方法Class类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

SpringApplication的核心构造方法有两个参数,第一个为ResourceLoader,ResourceLoader为资源加载的接口,默认采用的是DefaultResourceLoader。第二个为Class<?>… primarySources,默认传入Spring Boot入口类。作为项目引导类,此参数传入的类需要满足一个条件,就是被注解@EnableAutoConfiguration或其组合注解标注。

2.1、推断Web应用类型

赋值完成员变量后,接下来是推断Web应用类型,源码如下:

public enum WebApplicationType {

	// 非Web应用
	NONE,

	// 基于SERVLET的Web类型
	SERVLET,

	// 基于REACTIVE的Web类型
	REACTIVE;

	private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

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

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

	private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

	private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";

	private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

  	// 基于classpath中类是否存在来进行类型推断,即判断指定的类是否存在于classpath下,根据判断结果进行组合推断应用类型
	static WebApplicationType deduceFromClasspath() {
    	// isPresent的核心机制是通过反射创建指定的类,根据在创建过程中是否抛出异常来判断该类是否存在
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
      	// 当DispatcherHandler存在,并且DispatcherServlet和ServletContainer都不存在,则Web类型为REACTIVE
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
      	// 当Servlet或ConfigurableWebApplicationContext不存在时,则为非Web应用
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
    	// 走到这里说明,Servlet和ConfigurableWebApplicationContext都存在
		return WebApplicationType.SERVLET;
	}
}

2.2、ApplicationContextInitializer加载

Spring Boot使用的容器为ConfigurableApplicationContext,ApplicationContextInitializer是Spring IOC容器提供的一个接口,它是一个回调接口,主要用于用户在ConfigurableApplicationContext类型(或其子类型)的ApplicationContext做refresh方法调用刷新之前,对ConfigurableApplicationContext实例做进一步的设置或处理。

ApplicationContextInitializer接口源码为:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
    void initialize(C var1);
}

ApplicationContextInitializer接口的initialize方法主要是为了初始化指定的应用上下文。而对应的上下文由参数传入,参数为ConfigurableApplicationContext的子类。

ApplicationContextInitializer的加载分为两个步骤:获取相关实例和设置实例。对应的方法分别为getSpringFactoriesInstances、setInitializers,getSpringFactoriesInstances源码如下:

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}

	/**
	 * 用来获取spring.factories配置文件中的相关类, 并进行实例化操作
	 */
	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		// 获取类加载器
		ClassLoader classLoader = getClassLoader();
		// 加载META-INF/spring.factories中对应的配置, 并将结果存储于Set中, 方便去重
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		// 创建实例
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		// 排序
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

	/**
	 * 实例化注册类
	 */
	@SuppressWarnings("unchecked")
	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;
	}

getSpringFactoriesInstances方法依然是通过SpringFactoriesLoader类中的loadFactoryNames方法来获得META-INF/spring.factories文件中注册的对应配置,获取到这些配置类的全限定类名之后,通过反射生成实例。ApplicationContextInitializer的相关配置如下:

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

在完成配置类集合和实例化操作之后,调用setInitializers方法将实例化的集合添加到SpringApplication的成员变量initializers中,源码如下:

public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
		// 新建一个List,并将其复制给SpringApplication的成员变量initializers
		this.initializers = new ArrayList<>(initializers);
	}

2.3、ApplicationListener加载

完成了ApplicationContextInitializer的加载之后,便会进行ApplicationListener的加载。它的常见应用场景为:当容器初始化完成之后,需要处理一些如数据的加载、初始化缓存、特定任务的注册等操作。在此阶段,更多的是用于ApplicationContext管理Bean过程的场景。

Spring事件传播机制是基于观察者模式实现的。比如,在ApplicationContext管理Bean生命周期的过程中,会将一些改变定义为事件(ApplicationEvent),ApplicationContext通过ApplicationListener监听ApplicationEvent,当事件被发布之后,ApplicationListener用来对事件做出具体的操作。

ApplicationListener的这个配置和加载流程和ApplicationContextInitializer完全一致,也是通过SpringFactoriesLoaderloadFactoryNames方法获得META-INF/spring.factories中对应的配置,然后再进行实例化,这里不再赘述。

看一下ApplicationListener使用,ApplicationListener源码如下:

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
  	// onApplicationEvent方法用于处理应用程序事件,参数event为ApplicationEvent的子类,是具体接收到的事件
    void onApplicationEvent(E event);
}

从源码中可以看出,ApplicationListener接口和ApplicationEvent类配合使用,如果容器中存在ApplicationListener的Bean,当ApplicationContext调用publishEvent方法时,发布对应的事件,对应的Bean会被触发,即onApplicationEvent方法会被执行。

举个例子,当Application被初始化或刷新时,会触发ContextRefreshedEvent事件,可以实现一个ApplicationListener来监听该事件:

// 对该类进行Bean的实例化
@Component
public class ListenerDemo implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        // 打印容器里面初始化了多少个Bean
        System.out.println("监听器获得容器中初始化Bean的数量: " + contextRefreshedEvent.getApplicationContext().getBeanDefinitionCount());
    }
}

2.4、推断入口类

SpringApplication实例化的最后一步就是推断入口类,通过deduceMainApplicationClass进行推断,源码如下:

	private Class<?> deduceMainApplicationClass() {
		try {
			// 获取栈元素数组
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			// 遍历栈元素数组
			for (StackTraceElement stackTraceElement : stackTrace) {
				// 匹配第一个main方法, 并返回
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// 如果发生异常,忽略改一次,并继续执行
		}
		return null;
	}

该方法先创建一个运行时异常,然后获得栈数组,遍历栈数组,判断类的方法中是否包含main方法。一个被匹配到的类会通过Class.forName方法创建对象,并将其返回,最后在上层方法中将对象赋值给SpringApplication的成员变量mainApplicationClass。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值