对SpringApplication.run()中的前置处理方法prepareContext()方法的解析

本文详细解析了Spring Boot启动过程中的关键方法prepareContext的具体实现,包括各参数的作用、上下文环境的配置、监听器的使用及Bean的初始化过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

源码👇

	private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context);
		listeners.contextPrepared(context);
		bootstrapContext.close(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);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
		// 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);
	}

1.对参数列表分析

1.1

DefaultBootstrapContext bootstrapContext

1.2 run()方法中330行对其进行初始化启动上下文 

1.3查看createBootstrapContext()方法源码

private DefaultBootstrapContext createBootstrapContext() {
		DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
		this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
		return bootstrapContext;
	}

可以看到首先获取了一个DefaultBootstrapContext类实例 -> bootstrapContext,然后遍历this.bootstrappers对bootstrapContext进行初始化,由于this.bootstrappers中无任何元素,故对bootstrapContext未执行任何操作,最后将bootstrapContext返回。

我们查看bootstrapRegistryInitializers,发现其并无任何元素,所以直接返回。

 

1.2

 ConfigurableApplicationContext context

 我们发现context在run()方法的第340行进行了创建容器操作,继续进入createApplicationContext()👇

protected ConfigurableApplicationContext createApplicationContext() {
		return this.applicationContextFactory.create(this.webApplicationType);
	}
this.webApplicationType 此属性通过debug发现目前的值为SERVLET,当然这一部分有三个枚举属性

然后返回上下文对象即可;关于容器,上下文对象,这些含义和区别https://www.cnblogs.com/chenbenbuyi/p/8166304.html这篇文章写的很好;

1.3

ConfigurableEnvironment environment

environment是我们真正要使用的环境对象,已经完成了配置文件等的装载

 

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// Create and configure the environment
        //根据应用类型,创建应用环境:如得到系统的参数、JVM及Servlet等参数,等
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
		listeners.environmentPrepared(bootstrapContext, environment);
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = convertEnvironment(environment);
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}
getOrCreateEnvironment()👇
	private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		switch (this.webApplicationType) {
		case SERVLET:
			return new ApplicationServletEnvironment();
		case REACTIVE:
			return new ApplicationReactiveWebEnvironment();
		default:
			return new ApplicationEnvironment();
		}
	}

我们debug发现environment为null,所以if()判断不走,去下面的switch,当前的webApplicationType为“SERVLRET”,所以他返回的是ApplicationServletEnvironment。

//装配
configureEnvironment(environment, applicationArguments.getSourceArgs());
//发布环境已准备事件
listeners.environmentPrepared(bootstrapContext, environment);

1.4

SpringApplicationRunListeners listeners

在源码中333行, 跟踪一下

private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger,
				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
				this.applicationStartup);
	}

我们发现其中new了一个SpringApplicationRunListeners,其构造方法的参数又使用了getSpringFactoriesInstances()方法,我们继续跟一下👇

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),之前在@EnableAutoConfiguration中就使用过loadFactoryNames方法对Spring.factory配置文件进行获取,并且我们发现值是Set集合的形式,继续跟👇

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

重点看他使用的 loadSpringFactories(classLoaderToUse)👇

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

		result = new HashMap<>();
		try {
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			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;
	}

这个方法在我的@EnableAutoConfiguration中重点跟过,可以去那里了解细节,这里不赘叙。此方法就是将spring.properties配置文件中所包含的配置进行获取,返回的是Map集合类型,有很多配置,也就是我们使用@EnableAutoConfiguration自动配置获取到的全部配置!

回到1.4第一步的getRunListeners()中的getSpringFactoriesInstances()。现在,再结合getSpringFactoriesInstances()中createSpringFactoriesInstances()创建了SpringApplicationRunListener实例,并且在创建的时候,参数parameterTypes就是👇

Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);

获取的实例是getSpringFactoriesInstances()的形参SpringApplicationRunListener.class!

那么获取到持有SpringApplication的引用的这个new出来的SpringApplicationRunListeners()对象(getRunListeners里),返回

至此,👇方法返回的其获取到的监听器就跟完了。

这个监听器就是SpringApplicationRunListener,最后将其放在了SpringApplicationRunListeners容器中进行保存!

1.5 

ApplicationArguments applicationArguments

ApplicationArguments实例化是调用其实现类DefaultApplicationArguments,再传入main方法中args参数

我们继续跟踪new DefaultApplicationArguments(args)👇

public DefaultApplicationArguments(String... args) {
		Assert.notNull(args, "Args must not be null");
		this.source = new Source(args);
		this.args = args;
	}

源码我们发现,传递过来的参数ars被封装成了source对象

最终我们可以理解为,在run()方法中,监听器开启之后,获取main方法的参数args,会被封装进ApplicationArguments中。

1.6

Banner printedBanner

printerBanner是上面打印过的启动banner,进入方法实现,就是个图,没啥大用。

小结:

我们发现,run()方法上面几乎所有的方法的目的似乎都是为了prepareContext()方法服务

 2.对prepareContext()进行分析

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context);
		listeners.contextPrepared(context);
		bootstrapContext.close(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);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
		// 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);
	}

1.1首先看👇,他是将传递进来的environment设置到了容器中,替换新建容器时创建的environment.

1.2

postProcessApplicationContext(context);

首先看形参是我们获取到的AnnotationConfigServletWebServerApplicationContext,

进入方法内部👇 

	protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
		if (this.beanNameGenerator != null) {
			context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
					this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			if (context instanceof GenericApplicationContext) {
				((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
			}
			if (context instanceof DefaultResourceLoader) {
				((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
			}
		}
		if (this.addConversionService) {
			context.getBeanFactory().setConversionService(context.getEnvironment().getConversionService());
		}
	}

首先看SpringApplication对象中有没有自定义的BeanNameGenerator,有的话就注册到容器的单例池,这个对象是用来给容器中的Bean生成名字的,Spring容器new出来的时候会默认生成一个,默认的命名策略就是类名小写,不过SpringApplication中的该对象默认是null的

然后看SpringApplication对象有没有自定义ResourceLoader,有的话就赋值给容器,默认也是null的,不走判断,直接向下走

 

最后一个if分支,addConversionService在SpringApplication对象的构造函数里就默认设置为true,所以会走if,它为容器设置了一个ConversonService,这个类是用来做类型转换的,比如String转Integer等等。

 

这个方法拿到了上下文对象,主要的操作就是查看自定义BeanNameGenerator,有则注册到单例池和设置ConversonService,用来进行类型转换。

1.3

applyInitializers(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);
		}
	}

它取出了SpringApplication对象创建时,到META-INF/spring.factories中加载到的ApplicationContextInitializer列表,并依次调用其initialize方法

1.4

listeners.contextPrepared(context);

...太多了,不写了

SpringBoot源码解析(十四)prepareContext_一元咖啡的博客-CSDN博客_preparecontext

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你好,我是一名保安

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值