likes
comments
collection
share

Spring Boot微服务个人见解

作者站长头像
站长
· 阅读数 46

以前开发一个项目,要花费不少时间在搭建项目,配置文件上,到现在Spring Boot开箱即用,需要技术栈导入pom就可以了,技术变更带来效率提示是巨大的。有时候我会疑惑,这一切如何得来的,Spring Boot怎么抛弃war部署,抛弃繁琐xml配置。

阅读本文章需要一定的Spring框架知识储备,最后能了解Spring如何进行Bean初始化的,至少知道BeanDefinition之类的知识点,才能更好阅读文章。下面代码基于Spring Boot 2.7.2 、 Spring Cloud 2021.0.3。

先从项目启动放入入口,每一个Spring Boot 项目都需要main入口都要调用SpringApplication.run开始

	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);
	}
	public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}

	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
                //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();
	}

webApplicationType是一个枚举类,描述当前项目web类型 NONE: 当前项目不是一个web项目 SERVLET: 基于servlet api的传统web项目 REACTIVE: Spring webFlux 响应式web框架 deduceFromClasspath : 根据项目jar判断当前项目属于上面哪个一个类型,后面创建Spring 上下文对象需要用到 getSpringFactoriesInstances:从给定接口从文件META-INF/spring.factories 使用类名去加载全类名,并且返回接口所有实现类, 配置文件格式如下

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

这个类似JVM的SPI机制,对于Spring为什么没有使用SPI来 引入扩展实例,我猜SPI不满足多构造参数的实现类初始化,这里暂时将这种机制称作:SpringFactoriesLoader加载

BootstrapRegistryInitializer:用于初始化BootstrapRegistry的回调接口,在 使用BootstrapRegistry之前调用它。

ApplicationContextInitializer:在执行Spring工厂类调用AbstractApplicationContext.refresh(Spring 工厂核心方法bean初始化)之前初始化ConfigurableApplicationContext的回调接口。主要是做一个配置文件设置、属性设置。 ConfigurableApplicationContext 是一个SPI接口用于通过 配置方式初始化ApplicationContext 。Spring Boot作为Spring框架的集大成者上下文对象ApplicationContext往往根据不同环境有所区别的,这时很需要ApplicationContextInitializer这种接口,由不同组件根据自身情况去实现接口初始化上下文对象。

ApplicationContextInitializer接口

DelegatingApplicationContextInitializer: 通过环境变量 context.initializer.classes 类名,加载所有ConfigurdiableApplicationContext子类,实例化,排序执行ApplicationContextInitializer接口(接口参数)。 SharedMetadataReaderFactoryContextInitializer: 注册CachingMetadataReaderFactoryPostProcessor 用于向容器注册SharedMetadataReaderFactoryBean,用于缓存Spring加载资源 ContextIdApplicationContextInitializer: 初始化ContextId ConfigurationWarningsApplicationContextInitializer:报告@ComponentScan配置错误信息输入告警日志 RSocketPortInfoApplicationContextInitializer: 创建一个监听事件,将server.ports赋值到 local.rsocket.server.port ServerPortInfoApplicationContextInitializer: 创建web事件监听: 发布server namespace网络端口 ConditionEvaluationReportLoggingListener: 创建一个事件监听,spring初始化成功或失败,打印相关信息。

ApplicationListener列表

EnvironmentPostProcessorApplicationListener: 监听ApplicationEnvironmentPreparedEvent事件,执行EnvironmentPostProcessor 配置文件前置处理器,加载配置文件到ConfigurableEnvironment AnsiOutputApplicationListener: 监听Spring刚启动事件,从配置文件加载ansi配置。 LoggingApplicationListener: 加载日志相关配置进行初始化设置。 BackgroundPreinitializer: 通过多线程方式初始化Formatter、Validation、HttpMessageConverter、jackson、UTF-8设置。 DelegatingApplicationListener:从配置文件 key:context.listener.classes加载监听器类名并实例化注册到容器中 ParentContextCloserApplicationListener: 监听父级容器关闭事件,并且将事件传递到子级逐级传递下取。 ClearCachesApplicationListener: 清除类加器缓存 FileEncodingApplicationListener: 检测当前系统环境的file.encoding和spring.mandatory-file-encoding设置的值是否一样,如果不一样的话,就会抛出一个IllegalStateException异常,程序启动立马停止

run方法

	public ConfigurableApplicationContext run(String... args) {
		long startTime = System.nanoTime();
                //调用BootstrapRegistryInitializer接口对上下文进行初始化
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
                // 设置 java.awt.headless  缺失显示设备需要CPU介入显示
		configureHeadlessProperty();
                //获取事件发布器实例,这里会将上面监听器实例装进发布器,监听器类似事件消费者
		SpringApplicationRunListeners listeners = getRunListeners(args);
                //发布starting 事件
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
                        //获取所有启动参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                        //创建配置文件对象
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
                        //从配置文件中忽视bean
			configureIgnoreBeanInfo(environment);
                       //Banner  配置 打印
			Banner printedBanner = printBanner(environment);
                        //使用ApplicationContextFactory 初始化ApplicationContentx Spring 工厂
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
                       //配置文件对象配置
                       //开始对applicationContext context 进行初始化
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context); // 调用refresh
                        //空方法
			afterRefresh(context, applicationArguments);
			Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
			}
			listeners.started(context, timeTakenToStartup);
                        //调用所有 ApplicationRunner   CommandLineRunner
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}
		try {
			Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
			listeners.ready(context, timeTakenToReady);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

run()Spring Boot框架启动流程

  1. 获取Java 命令行启动参数,从中提取Spring 配置参数,转换从对应变量
  2. 创建配置文件对象ConfigurableEnvironment ,命令行中会有profile设置,所以要根据profile加载配置文件,在执行配置文件事件
  3. 已经加载好文件了,从环境变量中检测是否存在配置spring.beaninfo.ignore,如果设置,写入到ConfigurableEnvironment中
  4. 开始打印banner,平常看到各种banner就是在这里执行
  5. 开始创建ConfigurableApplicationContext ,Spring 容器工厂上下文对象
  6. 对刚刚创建ConfigurableApplicationContext 调用ApplicationContextInitializer 进行属性设置
  7. 启动Spring 容器IOC、AOP
  8. 发布Spring启动完成事件
  9. 从容器中所有ApplicationRunner CommandLineRunner在调用方法 在run方法里面就完成完成整个Spring容器启动流程了,包括Spring Cloud加载也是这里完成的。下面详细分析prepareEnvironment(),配置文件上下文如何初始化的

prepareEnvironment

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// 根据webApplicationType 创建
        // SERVLET =>  ApplicationServletEnvironment
        //REACTIVE=> ApplicationReactiveWebEnvironment
        // NONE => ApplicationEnvironment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 命令行可能会有profile,可以选择那个profile,也会将命令行参数生成一个PropertySources
		configureEnvironment(environment, applicationArguments.getSourceArgs());
        // 添加 configurationProperties  PropertySource到propertySourceList 队列最前面
		ConfigurationPropertySources.attach(environment);
        // 执行所有SpringApplicationRunListener
		listeners.environmentPrepared(bootstrapContext, environment);
        // 将defaultProperties sources 移致队尾
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
        // 从配置文件对应spring.main.* 属性注入
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
           //将类型转换器设置到environment
			environment = convertEnvironment(environment);
		}
        // 因为EnvironmentPostProcessor  可能加载到配置文件里,这时需要configurationProperties 放入第一
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

getOrCreateEnvironment() 如果当前environment如何为空,则会根据根据webApplicationType 类型选择对应类进行初始化。大家可能好奇environment怎么可能有值呢,接着玩下看,当我分析Spring Cloud时你就会返回environment不需要创建了。 ps: Environment 内部使用PropertySource区分不同配置文件,每一个源配置都有自己的名字,比如系统变量systemProperties、环境变量systemEnvironment等等。使用一个propertySourceList一个list将所有PropertySource保存起来,在队列前面永远最优先加载。

Spring Boot微服务个人见解 在上面写过一个监听器EnvironmentPostProcessorApplicationListener,它处理environmentPrepared事件,使用SpringFactoriesLoader加载所有EnvironmentPostProcessor 前置处理器,其中之一ConfigDataEnvironmentPostProcessor就是去做读取配置文件,里面还有很多逻辑处理,这里就不展开了,有兴趣的同学自行去分析代码。读取文件本身也是根据环境变量来的,这里有几个Spring内置配置

  • spring.config.location 设定加载文件路径,没有则是使用类路径./、config/
  • spring.config.additional-location: 加载外部文件路径,这个可以spring.config.location 共存,优先级最大
  • spring.config.name 设定文件名前置,默认 application 上面这些变量都是从环境变量、系统变量中获取的,当然不会从配置文件读取到。通过设定文件路径、文件名这样方式确定加载文件,加载文件规则如下

spring.config.location/{spring.config.location}/ spring.config.location/{spring.config.name}-profile.{profile}.profile.{extension}

  • extension:文件名后缀 内置支持4种,分别是: properties、yml、xml、yaml 看下ConfigurableApplicationContext 如何被初始化的

prepareContext

	private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
                // 初始化 ConfigurableEnvironment
		context.setEnvironment(environment);
                //初始化resourceLoader ConversionService
		postProcessApplicationContext(context);
                //执行上面从SpringFactoriesLoader加载 ApplicationContextInitializer  对ConfigurableApplicationContext 属性设置
		applyInitializers(context);
                 // 调用SpringApplicationRunListener.contextPrepared 事件
		listeners.contextPrepared(context);
                // 执行BootstrapContextClosedEvent 事件
		bootstrapContext.close(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		//下面添加特定单例对象,为Spring初始化bean IOC 处理必要的bean
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
                // 这里已经从配置文件加载 设置到自身属性上了,这时设置给上下文对象
                // allowCircularReferences 允许同名bean覆盖   lazyInitialization 对所有bean使用懒加载
		if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
			((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
			if (beanFactory instanceof DefaultListableBeanFactory) {
				((DefaultListableBeanFactory) beanFactory)
						.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
			}
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
                // 这个前置处理器主要作用就是将配置defaultProperties 移到队尾
		context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
		// Load the sources 这里有启动类
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
                // 初始化 BeanDefinitionLoader   并将启动类注册成BeanDefinition
		load(context, sources.toArray(new Object[0]));
                // 所有监听器执行contextLoaded 事件
		listeners.contextLoaded(context);
	}

在这里完成了Spring 容器初始化,下一步就是启动了。

bean初始化

其实我一直很好奇@Configuration这个注入如何实现配置类,还有还么多Class要被Spring进行初始化,如何变成BeanDefinition最后变成bean。我确定从AbstractApplicationContext.refresh()debug,终于被我发现Spring魔法,在invokeBeanFactoryPostProcessors() 在执行invokeBeanFactoryPostProcessors方法中回去获取BeanDefinitionRegistryPostProcessor类型内置对象,并且执行所有实现类。

  • BeanDefinitionRegistryPostProcessor: 你可以理解成BeanDefinition注册前置处理器,主要就是生成BeanDefinition,再还给容器。在Spring还没有初始化bean时,这个接口运行实现类去初始化BeanDefinition再交还给Spring工厂对象,简白点就是这个对象会创建BeanDefinition,交给Spring,后续进行初始化bean。下面要讲解其中一个实现类ConfigurationClassPostProcessor

postProcessBeanFactory创建postProcessBeanFactory

	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		int registryId = System.identityHashCode(registry);
		if (this.registriesPostProcessed.contains(registryId)) { //这个方法只能执行一次,通过记录上下文id标记执行
			throw new IllegalStateException(
					"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
		}
		if (this.factoriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + registry);
		}
		this.registriesPostProcessed.add(registryId);
               // 解析Class 生成BeanDefinition
		processConfigBeanDefinitions(registry);
	}

	/**
	 * Build and validate a configuration model based on the registry of
	 * {@link Configuration} classes.
	 */
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();
               //candidateNames 为前期使用BeanDefinitionRegistry 添加进去单例对象,除了拥有Spring 工厂对象外还有
               // SpringBoot main 启动类 这里能起到作用就是Spring Boot main 函数
		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
                       // 检查beanDef 是不是配置类,带有@Configuration都算
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

		// 这里不存在没有配置类,只有配置@SpringBootApplication Class 就是一个配置类
		if (configCandidates.isEmpty()) {
			return;
		}

		// Sort by previously determined @Order value, if applicable
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});

		// Detect any custom bean name generation strategy supplied through the enclosing application context
		SingletonBeanRegistry sbr = null;
		if (registry instanceof SingletonBeanRegistry) {
			sbr = (SingletonBeanRegistry) registry;
			if (!this.localBeanNameGeneratorSet) {
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
						AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
				if (generator != null) {
					this.componentScanBeanNameGenerator = generator;
					this.importBeanNameGenerator = generator;
				}
			}
		}

		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}

		// Parse each @Configuration class
                // ConfigurationClassParser 看名字就知道,这是一个解析@Configuration 解析类
                // 将解析Class 工作专门委派给parse去做了,解析后的结果会变成 ConfigurationClass
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do { //这里是一个循环
			StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
			parser.parse(candidates);
			parser.validate();
                        //已经将所有配置类全部解析出来 变成ConfigurationClass
			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			configClasses.removeAll(alreadyParsed); // 删除已经解析过

			// Read the model and create bean definitions based on its content
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
			this.reader.loadBeanDefinitions(configClasses); //将所有ConfigurationClass 转化BeanDefinition ,并注册到容器中
			alreadyParsed.addAll(configClasses); //添加已经注册过的,上面删除对应
			processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

			candidates.clear();
                        // 当ConfigurationClassParser  解析出ConfigurationClass 就会大于candidateNames
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
                                // bd 就是一个配置类
                                // bd 已经注册到容器中,但是不是在ConfigurationClassParser 解析出来的结果,则说明bd并没有通过解析生成
                               // 可能为第三方 BeanDefinitionRegistryPostProcessor 生成BeanDefinition,加入到candidates 再次进入循环中
                               //被ConfigurationClassParser 解析,可以生成更多BeanDefinition
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames;
			}
		}
		while (!candidates.isEmpty()); // 当所有BeanDefinition 都已经被解析完了,循环就可以退出了
        //下面省略


	}

看完上面的代码,ConfigurationClassPostProcessor就是Spring将带有@Configuration 标记Class经过一系列处理生成BeanDefinition的机制。在@SpringBootApplication 中有个一个@EnableAutoConfiguration带有@Import(AutoConfigurationImportSelector.class),这个会被ConfigurationClassPostProcessor解析加载。其中AutoConfigurationImportSelector使用SpringFactoriesLoader加载,会将所有@EnableAutoConfiguration的配置类全部都加载ClassName,可以让Spring Boot 加载ScanPackage 基础包路径之外的配置类,再通过@ConditionalOnBean、@ConditionalOnProperty这类注解,根据Class、配置判断是否进行解析。 也就是说Spring Boot一开始就已经获取到所有配置类,只有当符合条件时才会进入解析、加载、实例化。

Spring Cloud

上面说了Spring Boot自动化配置接下来就是Spring Cloud方面,看了上面源码,发现没有代码有关Spring Cloud,现在还不知道配置中心的配置如何作用到已经开始运行Spring 容器中。在开始分析代码之前,先简单看一个例子

Spring Boot微服务个人见解 可以看到applicatioinContext 有一个父级上下文,而这个就是Spring Cloud 上下文对象。看到这个是不是很惊奇,这个父级上下文在哪里初始化的呢,从代码角度去看了。 上面分析过ApplicationListener监听器中,在Spring Cloud lib jar中有一个实现类BootstrapApplicationListener,通过它来启动Spring Cloud。

	public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
		ConfigurableEnvironment environment = event.getEnvironment();
                // 两个条件  environment 配置 spring.cloud.bootstrap.enabled 或者某个类是否存在,其实就是 spring-cloud-starter-bootstrap jar class
                // 配置 spring.config.use-legacy-processing  这个配置是用来兼容旧版本配置文件加载
                //我这里环境引入spring-cloud-starter-bootstrap  第一个条件返回true,第二条件不用判断
		if (!bootstrapEnabled(environment) && !useLegacyProcessing(environment)) {
			return;
		}
		// don't listen to events in a bootstrap context
                // 判断environment 是否已经存在bootstrap 文件,已经加载过不需要往下执行了
                //当父级初始化也会执行监听器事件,到时来到这里时,父级监听器不会往下执行了
		if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
			return;
		}
		ConfigurableApplicationContext context = null;
                // 默认配置文件名,没有在环境变量配置默认就是bootstrap
		String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
		for (ApplicationContextInitializer<?> initializer : event.getSpringApplication().getInitializers()) {
			if (initializer instanceof ParentContextApplicationContextInitializer) { //在已经存在ParentContextApplicationContextInitializer 中返回父级容器
				context = findBootstrapContext((ParentContextApplicationContextInitializer) initializer, configName);
			}
		}
		if (context == null) { //当上面ParentContextApplicationContextInitializer 没有执行就会走下面初始化父级容器方法
                        // 这里会返回父级容器,也就是Spring Cloud 上下文对象
			context = bootstrapServiceContext(environment, event.getSpringApplication(), configName);
			event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
		}
                 //从父级容器中获取ApplicationContextInitializer 交给SpringApplication
                 //父级生成ApplicationContextInitializer 用于增强子类
		apply(context, event.getSpringApplication(), environment);
	}

这个监听器主要根据配置文件信息来启动Spring Cloud组件,如果没有相应的配置根据项目环境来,看下Spring Cloud上下文如何被初始化出来的。

	private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment,
			final SpringApplication application, String configName) {
		ConfigurableEnvironment bootstrapEnvironment = new AbstractEnvironment() {
		};
		MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
		String configLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
		String configAdditionalLocation = environment
				.resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
		Map<String, Object> bootstrapMap = new HashMap<>();
                // 使用代码生成一个Spring Cloud加载文件的配置信息,规则类似上面加载applicaton 配置
		bootstrapMap.put("spring.config.name", configName);
		bootstrapMap.put("spring.main.web-application-type", "none");
		if (StringUtils.hasText(configLocation)) {
			bootstrapMap.put("spring.config.location", configLocation);
		}
		if (StringUtils.hasText(configAdditionalLocation)) {
			bootstrapMap.put("spring.config.additional-location", configAdditionalLocation);
		}
                //将加载文件的配置信息放入配置文件上下文 environment
		bootstrapProperties.addFirst(new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
		for (PropertySource<?> source : environment.getPropertySources()) {
			if (source instanceof StubPropertySource) {
				continue;
			}
			bootstrapProperties.addLast(source);
		}
		// TODO: is it possible or sensible to share a ResourceLoader?
                // SpringApplicationBuilder  为SpringApplication 包装类,重新生成SpringApplication来创建ApplicationContext 上下文
		SpringApplicationBuilder builder = new SpringApplicationBuilder().profiles(environment.getActiveProfiles())
				.bannerMode(Mode.OFF).environment(bootstrapEnvironment)
				// Don't use the default properties in this builder
				.registerShutdownHook(false).logStartupInfo(false).web(WebApplicationType.NONE);
		final SpringApplication builderApplication = builder.application();
		if (builderApplication.getMainApplicationClass() == null) {
			builder.main(application.getMainApplicationClass());
		}
		if (environment.getPropertySources().contains("refreshArgs")) {
			builderApplication.setListeners(filterListeners(builderApplication.getListeners()));
		}
        / BootstrapImportSelectorConfiguration
		builder.sources(BootstrapImportSelectorConfiguration.class);
               // 这里将调用SpringApplication.run 上面已经分析,
		final ConfigurableApplicationContext context = builder.run();
		context.setId("bootstrap");
                //这里添加AncestorInitializer 是一个ApplicationContextInitializer 实现类,目的就是让子applicationContext 和父级关联起来
		addAncestorInitializer(application, context);
                //当前environment 为子集配置对象,这里要删除掉父级加载文件信息
		bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
                //将 springCloudDefaultProperties  配置文件信息copy到environment 中
		mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
		return context;
	}

现在所有代码都看完了,我们来理一理整一个流程就会清晰明了。 在BootstrapApplicationListener中会根据配置文件或者是项目环境jar来是否启动加载bootstrap配置文件。先从生成加载Spring Cloud配置信息, 使用SpringApplicationBuilder来构建SpringApplication对象,然后执行SpringApplication.run 方法,这个代码我们已经分析过了,初始化Spring容器上下文对象,然后进入核心refresh方法执行IOC。SpringApplicationBuilder构造SpringApplication 中没有像我们写启动类main方法,会设置启动类Class。所以被ConfigurationClassPostProcessor解析BeanDefinition,并没有@SpringApplication 这个注解,所以这个Spring Cloud 工厂没有获取到basepackae、@EnableAutoConfiguration这些东西。根据上面代码知道Spring Cloud将BootstrapImportSelectorConfiguration 作为BeanDefinition交给ConfigurationClassPostProcessor,这样父级容器只有加载BootstrapConfiguration标记类,父级bean和子级bean相互隔离。这样父级容器就可以去启动与Spring Cloud有关的bean。当Spring Cloud容器已经完成bean初始化后,再来执行SpringApplicaton.run 启动Spring 容器创建。这样在子级启动之前已经将配置中心的配置对应的对象已经创建出来了。再通过ApplicationContextInitializer接口将配置对象加载ConfigurableEnvironment中。

这里使用较短的篇幅来分析Spring Boot这个框架如何工作,站在自己的思维上,使用3个知识点来展示Spring Boot技术细节实现。第一个从SpringApplication.run了解Spring两大工厂对象ConfigurableApplicationContextConfigurableEnvironment如何初始化处理出来的,配置文件如何被加载的,加载规则,知识点SpringFactoriesLoader机制,如果要做Spring Boot组件必须要这个。了解了Spring Boot ApplicationContextInitializer、ApplicationListener这些接口,还有SpringApplicationRunListener为整个Spring Boot事件监听器,对应整个框架的不同阶段处理。第二简单分析了 Spring容器启动时如何生成BeanDefinition的机制实现类:BeanDefinitionRegistryPostProcessor,了解了Spring Boot组件如何被加载、实例化,这个依赖启动类的注解。最后Spring Cloud组件如何加载实例化,这个依赖于前面两个。

转载自:https://juejin.cn/post/7148073247412584484
评论
请登录