SpringApplication对象的构建及spring.factories的加载时机

构建SpringApplication对象源码:

1、调用启动类的main()方法,该方法中调用SpringApplication的run方法。

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

2、调用SpringApplication的run()方法的重载方法,在发方法内构建了SpringApplication对象

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

3、构建SpringApplication对象。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		//resourceLoader为null
		this.resourceLoader = resourceLoader;
		//PrimarySources(即启动类)一定不能为null
		Assert.notNull(primarySources, "PrimarySources must not be null");
		//初始化primarySources属性
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		//从Classpath中推断Web应用类型。
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		this.bootstrapRegistryInitializers = new ArrayList<>(
				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

构建过程:

接下来我们来详细研究一下上述过程。在构建SpringApplication对象过程中,此时resourceLoader

为null,primarySources一定不为空且需要初始化为启动类SpringbootdemoApplication

。从Classpath中推断出Web应用类型并初始化webApplicationType,通过getSpringFactoriesInstances()获取Spring工厂实例来初始化bootstrapRegistryInitializers,initializers(List<ApplicationContextInitializer<?>>应用程序上下文初始化器列表), listeners(List

4、从Classpath推断Web应用类型,调用的是WebApplicationType类的deduceFromClasspath()方法。如果Classpath中包含DispatcherHandler,则表明当前WebApplicationType为REACTIVE;如果既不包含javax.servlet.Servlet也不包含Spring中的ConfigurableWebApplicationContext,则表明当前WebApplicationType为NONE; 上述两种都不是则表明当前WebApplicationType是SERVLET

static WebApplicationType deduceFromClasspath() {
        // 如果org.springframework.web.reactive.DispatcherHandler的class文件存在且可以加载
        //不存在org.springframework.web.servlet.DispatcherServlet
        //不存在org.glassfish.jersey.servlet.ServletContainer
        //则返回REACTIVE表明 该应用程序应作为响应式Web应用程序运行,并应启动嵌入式servlet Web 服务器
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		//遍历SERVLET 指标类
		//如果不存在javax.servlet.Servlet也不存在org.springframework.web.context.ConfigurableWebApplicationContext
		//则返回NONE表明该应用程序不应作为 Web 应用程序运行,也不应启动嵌入式 Web 服务器
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		//返回SERVLET表明该应用程序应作为基于servlet的 Web 应用程序运行,并应启动嵌入式 servlet Web 服务器。
		return WebApplicationType.SERVLET;
	}

spring.factories的加载时机

1、在构建SpringApplication中,初始化其属性bootstrapRegistryInitializers属性时进行加载 /META-INF/spring.factories。

2、构建SpringApplication对象时,通过调用getSpringFactoriesInstances(Class type)获取工厂实例。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		......
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		this.bootstrapRegistryInitializers = new ArrayList<>(
				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	    ......
	}

3、我们以初始化bootstrapRegistryInitializers为例讲解,getSpringFactoriesInstances(Class type, Class<?>[] parameterTypes, Object... args)中首先获取ClassLoader ,通过SpringFactoriesLoader机制,根据ClassLoader从类路径jar包中加载META-INF/spring.factories下的所有工厂类及其实现类 。

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 classLoader = getClassLoader();
		// 使用SpringFactoriesLoader机制加载出工厂名,并放入Set集合中确保其唯一性。但是在META-INF/spring.factories中并无BootstrapRegistryInitializer所以此处的names的size为0。
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		//创建Spring工厂实例(但因为上述names的size为0,所以对于BootstrapRegistryInitializer来说它并不会创建SpringFactory实例)
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		//通过AnnotationAwareOrderComparator对Spring工厂实例排序
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

4、SpringFactoriesLoader中的loadFactoryNames来加载META-INF/spring.factories下的所有工厂类及其实现类

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		//获取当前使用的ClassLoader
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
		    //为空,则获取SpringFactoriesLoader的类加载器
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		//org.springframework.boot.BootstrapRegistryInitializer
		String factoryTypeName = factoryType.getName();
		//加载META-INF/spring.factories下的扩展类,没有值的返回一个空列表。
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

5、重点关注SpringFactoriesLoader中的loadSpringFactories(ClassLoader classLoader),该方法具体实现了从META-INF/spring.factories下加载扩展类。

//工厂类所在位置,在多个jar文件中都有。
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        //检查缓存中是否有工厂类
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
		//缓存中没有,初始化result
		result = new HashMap<>();
		try {
		    //加载META-INF/spring.factories下的一系列元素
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			//迭代遍历
			while (urls.hasMoreElements()) {
			    //spring-boot-版本号.jar文件中Spring.factories所在的绝对路径
				URL url = urls.nextElement();
				//构建UrlResource
				UrlResource resource = new UrlResource(url);
				//加载该UrlResource中的属性(即Spring.factories中的键值对)
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
				    //父类工厂类型名
					String factoryTypeName = ((String) entry.getKey()).trim();
					//子类工厂实现类名数组(在Spring.factories中多个用逗号分隔)
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					//将工厂类型名,工厂实现类列表放入名为result的 Map<String, List<String>>中,key为工厂类型名,value为工厂实现类列表。
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			//将result中的所有list都置为包含唯一元素的不可修改的list
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			//放入缓存。		
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

bootstrapRegistryInitializers 的初始化中实现了加载META-INF/spring.factories中工厂扩展类(但是在META-INF/spring.factories并无bootstrapRegistryInitializers ),并将其放入缓存Map<String, List>,其中key为父类工厂名,value为其对应扩展类列表。之后initializers(ApplicationContextInitializer列表)以及listeners(ApplicationListener列表)的初始化都是从该缓存中获取值。

6、接下里我们看一下第三步中的createSpringFactoriesInstances()方法。由于bootstrapRegistryInitializers 在META-INF/spring.factories中并不存在。所以我们只有它返回的instances是空的即它不会创建SpringFactoriesInstances。但是初始化initializers,listeners时,却会。我们看一下具体源码。

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set<String> names) {
		//初始化names.size()大小的list存放创建出来的实例。
		List<T> instances = new ArrayList<>(names.size());
		//遍历传入的工厂扩展类实例名
		for (String name : names) {
			try {
			    //通过反射获取instance的Class对象
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				//instanceClass是一个type类型,向下,否则抛异常IllegalArgumentException
				Assert.isAssignable(type, instanceClass);
				//获取instanceClass的构造器
				Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
				//通过BeanUtils的instantiateClass()方法实例化类。
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
			    //将实例化出来的类放入instances
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}
相关推荐
aloha_78914 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
阑梦清川18 小时前
JavaEE进阶---第一个SprintBoot项目创建过程&&&我的感受
java·java-ee·springboot
A-bodgie19 小时前
Spring 中的 Environment 对象
java·后端·spring·servlet·springboot
小布布的不1 天前
MyBatis 返回 Map 或 List<Map>时,时间类型数据,默认为LocalDateTime,响应给前端默认含有‘T‘字符
前端·mybatis·springboot
小沈同学呀1 天前
Mac M1 Docker创建Rocketmq集群并接入Springboot项目
macos·docker·java-rocketmq·springboot
东皋长歌2 天前
SpringBoot+ClickHouse集成
clickhouse·springboot
程序员徐师兄2 天前
基于 JavaWeb 的宠物商城系统(附源码,文档)
java·vue·springboot·宠物·宠物商城
果冻眼企鹅2 天前
常见用于从 HTTP 请求中提取数据的注解
springboot
你白勺男孩TT3 天前
Vue项目中点击按钮后浏览器屏幕变黑,再次点击恢复的解决方法
vue.js·vue·springboot
小云小白3 天前
springboot 传统应用程序,适配云原生改造
云原生·系统架构·k8s·springboot