前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Apollo与SpringBoot整合原理深度剖析

Apollo与SpringBoot整合原理深度剖析

作者头像
大忽悠爱学习
发布2023-05-23 10:12:09
7700
发布2023-05-23 10:12:09
举报
文章被收录于专栏:c++与qt学习

Apollo与SpringBoot整合原理深度剖析

Spring环境上下文模块

  • Spring Envionment设计如下:
在这里插入图片描述
在这里插入图片描述
  • 需要注意的是,PropertySource之间是有优先级顺序的,如果有一个Key在多个property source中都存在,那么在前面的property source优先。
代码语言:javascript
复制
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
    //Spring中并不是直接用一个List<PropertySource>管理多个属性源,而是用一个MutablePropertySources进行管理
	private final MutablePropertySources propertySources = new MutablePropertySources(this.logger); 
	...
}
//MutablePropertySources中本质还是使用一个List集合进行管理---组合模式的体现
public class MutablePropertySources implements PropertySources {
	//一个写时复制集合,考虑到了线程安全性,同时兼顾了性能,因为对于PropertySource而言,读大于写,一般属性源在Spring应用程序启动完毕后,基本就确定不动了
	private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    ...
}
  • Spring中的Environment模块组织结构如下所示:
在这里插入图片描述
在这里插入图片描述
  • ApplicationContext继承EnvironmentCapable接口,表明具备管理和对外暴露环境上下文对象的能力
  • 普通的Spring Bean可以通过继承EnvironmentAware,在依赖注入阶段,从容器中获取并注入环境上下文实例对象
  • PropertyResolver接口对外暴露获取解析过后的属性的相关方法 --> 只负责解析 ${} 占位符 , 不负责解析EL表达式#{}占位符
  • Environment接口继承了PropertyResolver接口,从而具备了获取解析过后的属性的能力,并且自身对外暴露获取激活配置文件和默认配置文件方法

这里Environment根据配置文件中设置的spring.active.active属性来管理当前激活的Profile(配置文件)

  • ConfigurableEnvironment接口主要对外暴露修改激活的Profile和默认Profile的方法,同时还对外暴露出MutablePropertySources属性源,SystemProperties属性源和SystemEnvironment属性源,用户获取后可以进行修改

Spring很多接口都是读写分离的,最顶层接口一般都只会提供只读方法,Configurablexxx接口中提供写方法,这是Spring框架设计的一般规律之一

  • AbstractEnvironment对顶层读写接口功能进行具体落地实现,同时也作为抽象基础类将属性源管理和profile管理全部实现,但是具体是标准环境上下文,Web环境上下文还是响应式Web环境上下文,给出不同实现类区分即可
代码语言:javascript
复制
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
    //管理激活配置文件的容器    
	private final Set<String> activeProfiles = new LinkedHashSet<>();
   //管理默认配置文件的容器
	private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
    //MutablePropertySources负责管理多个属性源---组合模式体现
	private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);
    //负责${}占位符属性解析
	private final ConfigurablePropertyResolver propertyResolver =
			new PropertySourcesPropertyResolver(this.propertySources);
    
    public AbstractEnvironment() {
        //留给子类扩展其他属性源的钩子方法
		customizePropertySources(this.propertySources);
	}
    ...
}    

  • Environment返回的是解析后的属性,因此所有的getProperty的系列方法,实际都是由propertyResolver负责完成。
  • propertyResolver负责search and parse
    • search是根据key遍历属性源集合找到第一个存在该key的propertySource并返回value
    • parse是对找到的value进行${}占位符解析,最终返回的是parse过后的value
  • StandardEnvironment代表标准环境上下文,可以在非Web和Web环境下使用
代码语言:javascript
复制
public class StandardEnvironment extends AbstractEnvironment {
	public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
	public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
    //StandardEnvironment只干了一件事,通过钩子接口customizePropertySources向属性源集合添加系统属性源和系统上下文属性源
	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(
				new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		propertySources.addLast(
				new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
	}
}

Java环境变量(Env)和系统属性(Property)区别介绍

  • StandardServletEnvironment代表Servlet作为Web容器环境下的上下文
代码语言:javascript
复制
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
	public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
	public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
	public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";
    //StandardServletEnvironment同样是通过钩子接口,向属性源集合中添加上面三个属性源
	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
	    //此处的Stub意思为桩,表示模拟属性源--在initPropertySources方法中会对Stub属性源进行实际替换操作
	    //之所以这里采用Stud模拟属性源,因为环境上下文模块初始化早于Servlet容器初始化和启动
		propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
		propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
		if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
			propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
		}
		//调用父类钩子方法,添加系统属性源和系统上下文属性源
		super.customizePropertySources(propertySources);
	}
    
    //initPropertySources方法来源于ConfigurableWebEnvironment接口,当Servlet容器启动时,调用此方法完成上面两个stub属性源的替换
    //initPropertySources回调接口何时被调用,大家可以追踪一下SpringBoot环境下Servlet容器初始化过程
	@Override
	public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig){
		WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
	}
}
  • initServletPropertySources方法完成模拟属性源替换工作
在这里插入图片描述
在这里插入图片描述
  • initPropertySources调用链
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

@Value注解如何完成属性注入

  • 回顾一下Bean的生命周期
在这里插入图片描述
在这里插入图片描述
  • 依赖注入阶段是如何完成属性注入的呢 ?
在这里插入图片描述
在这里插入图片描述
  • AutowiredAnnotationBeanPostProcessor的postProcessPropertyValues方法负责处理由注解形式指定的依赖注入点集合

需要依赖注入的点指定方式在Spring中有两种方式,一种是配置文件方式,一种是注解形式。

  • 配置文件方式指定的依赖注入点集合在populateBean方法中,由BeanWrapper利用setter方法完成依赖注入
  • 注解方式指定的依赖注入点集合在populateBean方法中,由SmartInstantiationAwareBeanPostProcessor的postProcessPropertyValues方法进行处理,默认通过反射方式完成依赖注入
  • 如果配置文件和注解指定的依赖注入点集合中存在重叠的注入点,虽然注解指定的依赖注入点先被处理,但是postProcessPropertyValues方法中会判断当前注入点是否已经在配置文件中指定,如果指定了,那么跳过处理。
代码语言:javascript
复制
public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
		implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
	private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4);
	private String requiredParameterName = "required";
	private boolean requiredParameterValue = true;
	private ConfigurableListableBeanFactory beanFactory;
	private final Map<String, InjectionMetadata> injectionMetadataCache = new ConcurrentHashMap<>(256);

	public AutowiredAnnotationBeanPostProcessor() {
	    //当前Bean后置处理器负责处理Autowired,Value,Inject注解
		this.autowiredAnnotationTypes.add(Autowired.class);
		this.autowiredAnnotationTypes.add(Value.class);
		try {
			this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
					ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
			logger.info("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
		}
		catch (ClassNotFoundException ex) {
		}
	}
    
    //处理注解指定的依赖注入
	@Override
	public PropertyValues postProcessPropertyValues(
			PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException{
        //寻找当前bean属性和方法上所有标注了相关注解的地方,如果找到了,则封装为一个InjectedElement对象实例
        //表示待注入元素,这里待注入元素可以是字段,或者方法
        //InjectionMetadata内部管理从当前bean上找到的所有InjectedElement
		InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
		try {
		    //进行注解标注的依赖注入---遍历InjectionMetadata内部管理的InjectedElement集合
		    //依次调用每个InjectedElement的inject方法
			metadata.inject(bean, beanName, pvs);
		}
		...
		//
		return pvs;
	}
    ...

	private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs){
		// InjectionMetadata会被缓存起来--key为beanName
		String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
		InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
		//双重锁机制,确保单例
		if (InjectionMetadata.needsRefresh(metadata, clazz)) {
			synchronized (this.injectionMetadataCache) {
				metadata = this.injectionMetadataCache.get(cacheKey);
				if (InjectionMetadata.needsRefresh(metadata, clazz)) {
					if (metadata != null) {
						metadata.clear(pvs);
					}
					//为当前bean构建InjectionMetadata
					metadata = buildAutowiringMetadata(clazz);
					this.injectionMetadataCache.put(cacheKey, metadata);
				}
			}
		}
		return metadata;
	}
   
	private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
		List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
		Class<?> targetClass = clazz;
		do {
			final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
			//遍历当前类上所有属性,寻找到存在相关注解的属性
			ReflectionUtils.doWithLocalFields(targetClass, field -> {
				AnnotationAttributes ann = findAutowiredAnnotation(field);
				//排除静态属性的注入
				if (ann != null) {
					if (Modifier.isStatic(field.getModifiers())) {
						if (logger.isWarnEnabled()) {
							logger.warn("Autowired annotation is not supported on static fields: " + field);
						}
						return;
					}
					//从注解中取出required属性--表明是否必须注入成功
					boolean required = determineRequiredStatus(ann);
					//封装为AutowiredFieldElement后返回
					currElements.add(new AutowiredFieldElement(field, required));
				}
			});
            //遍历当前bean所有方法,寻找存在相关注解的方法,并且方法不是静态的,封装为AutowiredMethodElement后返回
			ReflectionUtils.doWithLocalMethods(targetClass, method -> {
				...
				AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
				if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
					if (Modifier.isStatic(method.getModifiers())) {
						...
						return;
					}
					...
					boolean required = determineRequiredStatus(ann);
					PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
					currElements.add(new AutowiredMethodElement(method, required, pd));
				}
			});
			elements.addAll(0, currElements);
			//注入依赖注入一并处理当前父类上标注的相关依赖注入点
			targetClass = targetClass.getSuperclass();
		}
		while (targetClass != null && targetClass != Object.class);
        //创建一个InjectionMetadata返回,InjectionMetadata管理当前bean中所有依赖注入点
		return new InjectionMetadata(clazz, elements);
	}	    
}
  • InjectionMetadata的inject方法负责完成实际的基于注解搜集到依赖注入点的依赖注入
代码语言:javascript
复制
	public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
		    ...
		    //遍历所有InjectedElement,调用其inject方法完成依赖注入
			for (InjectedElement element : elementsToIterate) {
				...
				element.inject(target, beanName, pvs);
			}
		}
	}

依赖注入点InjectedElement分为两种实现类: AutowiredFieldElement和AutowiredMethodElement,此处只看AutowiredMethodElement的inject方法实现

在这里插入图片描述
在这里插入图片描述
  • AutowiredMethodElement的inject方法进行方法级别的依赖注入之前,会调用checkPropertySkipping方法检查是否和配置文件中指定的依赖注入点重叠,如果是,则跳过处理。
代码语言:javascript
复制
	private class AutowiredFieldElement extends InjectionMetadata.InjectedElement {

		private final boolean required;

		private volatile boolean cached = false;

		@Nullable
		private volatile Object cachedFieldValue;
        ...
		@Override
		protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
			Field field = (Field) this.member;
			Object value;
			 ...
            //构建依赖描述符
		    DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
			  ...
             //类型转换器
		     TypeConverter typeConverter = beanFactory.getTypeConverter();
             //调用beanFactory的resolveDependency方法,根据依赖描述符完成依赖查找
			 value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
				...
            //反射注入        
			if (value != null) {
				ReflectionUtils.makeAccessible(field);
				field.set(bean, value);
			}
		}
	}

  • resolveDependency作为依赖查找核心,怎么查找的?
在这里插入图片描述
在这里插入图片描述
  • DefaultListableBeanFactory的resolveDependency方法作为IOC容器进行依赖查找的核心方法
代码语言:javascript
复制
	public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
        //初始化方法参数名解析器
		descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
		...
		//如果当前依赖注入点上存在@Lazy注解,则创建一个代理对象返回,为的是实现依赖延迟注入---这块逻辑很简单,不展开,自己看源码
		Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);
	     //正常情况下进入依赖查找逻辑
	    if (result == null) {
				result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
		 }
		return result;
	}
}

这里是引用
这里是引用
  • doResolveDependency方法中真正负责依赖查找工作,依赖查找要么根据value注解中的属性值进行解析,把解析后的结果作为依赖查找结果返回。要么就是@Autowired属性注入–> 也就是我们平常认为的依赖查找应该干的事情。
代码语言:javascript
复制
	public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
        
		InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
		try {
		    //描述符解析过了,直接返回--相当于缓存 
			Object shortcut = descriptor.resolveShortcut(this);
			if (shortcut != null) {
				return shortcut;
			}
            //获取要查找的bean类型---当然这里可能是bean类型(@Autowired注解),也可能需要查找的是String字符串(@Value注解)
			Class<?> type = descriptor.getDependencyType();
			//当前依赖注入点上是否存在@Value注解--如果存在返回注解中value属性值,否则返回null
			Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
			//如果value不为null,说明当前依赖查找需要解析value注解中的value属性值,然后返回作为依赖查找结果
			if (value != null) {
				if (value instanceof String) {
				     //解析${}占位符
					String strVal = resolveEmbeddedValue((String) value);
					BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
					//解析EL表达式
					value = evaluateBeanDefinitionString(strVal, bd);
				}
				//获取类型转换器
				TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
				//@Value注解可以标注在Resource资源对象上,因为value字符串经过${}占位符解析和EL表达式解析后
				//可能得到的是一个资源路径或者url字符串表示形式,可以使用类型转换器将字符串形式表示的资源路径转换为
				//实际的资源类型对象
				return (descriptor.getField() != null ?
						converter.convertIfNecessary(value, type, descriptor.getField()) :
						converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
			}
			//下面是@Autowired等注解的依赖查找过程,非本文重点,这里不进行讲解
            ...
		}
		finally {
			ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
		}
	}

EL表达式的解析部分本文也不展开讲解

  • resolveEmbeddedValue方法解析${}占位符
代码语言:javascript
复制
	public String resolveEmbeddedValue(@Nullable String value) {
		if (value == null) {
			return null;
		}
		String result = value;
		for (StringValueResolver resolver : this.embeddedValueResolvers) {
			result = resolver.resolveStringValue(result);
			if (result == null) {
				return null;
			}
		}
		return result;
	}

AbstractBeanFactory的embeddedValueResolvers集合中StringValueResolver 的注册时机是什么呢?


  • StringValueResolver的注册时机(有两处,另一处---->PropertyResourceConfigurer#processProperties–>PropertyResourceConfigurer#postProcessBeanFactory)
代码语言:javascript
复制
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, DisposableBean {

	protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
		...
		// 若没有指定EmbeddedValueResolver
		if (!beanFactory.hasEmbeddedValueResolver()) {
		    //创建一个匿名内部类的实现,核心交给环境上下文对象完成
			beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
				@Override
				public String resolveStringValue(String strVal) {
					return getEnvironment().resolvePlaceholders(strVal);
				}
			});
		}
		...
	}
}
在这里插入图片描述
在这里插入图片描述
  • AbstractEnvironment的resolvePlaceholders方法实现最终是交给PropertySourcesPropertyResolver完成的
在这里插入图片描述
在这里插入图片描述
  • AbstractPropertyResolver抽象类中给出resolvePlaceholders方法实现
代码语言:javascript
复制
	@Override
	public String resolvePlaceholders(String text) {
	    //这里的strict表示是否忽略无法被解析的${}占位符,如果不忽略,则会抛出异常
		if (this.nonStrictHelper == null) {
			this.nonStrictHelper = createPlaceholderHelper(true);
		}
		// 真正进行占位符解析
		return doResolvePlaceholders(text, this.nonStrictHelper);
	}
   
	private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
		//借助PropertyPlaceholderHelper完成占位符解析
		return helper.replacePlaceholders(text, this::getPropertyAsRawString);
	}	
    
    //子类实现根据PropertyPlaceholderHelper取出${server.port}占位符中的server.port
    //去属性源集合中根据取出的server.port作为key,进行查找
	protected abstract String getPropertyAsRawString(String key);	
  • 占位符核心解析逻辑在PropertyPlaceholderHelper的parseStringValue方法中
代码语言:javascript
复制
    //传入的是value注解中的value属性值,如: ${server.port}
    public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
		Assert.notNull(value, "'value' must not be null");
		return parseStringValue(value, placeholderResolver, new HashSet<>());
	}
     
	protected String parseStringValue(
			String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
		StringBuilder result = new StringBuilder(value);
		//定位 ${ 出现的位置
		int startIndex = value.indexOf(this.placeholderPrefix);
		while (startIndex != -1) {
		    //定位 } 出现的位置 
			int endIndex = findPlaceholderEndIndex(result, startIndex);
			if (endIndex != -1) {
			    //截取${server.post}占位符之间的值--> server.port
				String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
				//拿到传入的占位符解析器
				String originalPlaceholder = placeholder;
				...
				// 递归解析${}占位符,因为可能会存在${${config.key}.value}的情况
				placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
				// 调用传入的占位符解析器的resolvePlaceholder方法解析占位符
				String propVal = placeholderResolver.resolvePlaceholder(placeholder);
				//valueSeparator默认为":",表示默认值
				//如果val值为null,那么尝试查找默认值
				if (propVal == null && this.valueSeparator != null) {
				   //如: ${server.port:8079}---如果存在默认值设置,那么将默认值作为结果
					int separatorIndex = placeholder.indexOf(this.valueSeparator);
					if (separatorIndex != -1) {
						String actualPlaceholder = placeholder.substring(0, separatorIndex);
						String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
						propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
						if (propVal == null) {
							propVal = defaultValue;
						}
					}
				}
				//如果解析结果不为null
				if (propVal != null) {
					//考虑到根据${dhy.name}中的dhy.name查找属性源得到的值可能是${xpy.name}这种形式
					//因此需要继续递归解析
					propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
					result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
					...
					startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
				}
				else if (this.ignoreUnresolvablePlaceholders) {
					// Proceed with unprocessed value.
					startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
				}
				//ignoreUnresolvablePlaceholders为false的情况下,抛出解析失败异常
				else {
					throw new IllegalArgumentException("Could not resolve placeholder '" +
							placeholder + "'" + " in value \"" + value + "\"");
				}
				visitedPlaceholders.remove(originalPlaceholder);
			}
			else {
				startIndex = -1;
			}
		}
		return result.toString();
	}
  • PropertySourcesPlaceholdersResolver提供的resolvePlaceholder方法,根据PropertyPlaceholderHelper取出${server.port}占位符中的server.port,去属性源集合中根据取出的server.port作为key,进行查找
代码语言:javascript
复制
	protected String resolvePlaceholder(String placeholder) {
		if (this.sources != null) {
		   //挨个遍历每个属性源,哪个先找到,就直接返回---这里引出了配置优先级问题 
			for (PropertySource<?> source : this.sources) {
				Object value = source.getProperty(placeholder);
				if (value != null) {
					return String.valueOf(value);
				}
			}
		}
		return null;
	}

application.yml等配置文件加载时机

在这里插入图片描述
在这里插入图片描述
  • SpringApplication的构造函数中通过SPI完成ApplicationContextInitializer和ApplicationListener实现类的加载
代码语言:javascript
复制
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}
  • 这里重点关注通过SPI加载得到的,专门负责加载配置文件的监听器
在这里插入图片描述
在这里插入图片描述
  • SpringApplication的Run方法启动SpringBoot应用程序—这里只关注和配置文件加载相关的监听器部分
代码语言:javascript
复制
	public ConfigurableApplicationContext run(String... args) {
		..
		ConfigurableApplicationContext context = null;
		...
		//SPI加载SpringApplicationRunListener实现类,然后交给SpringApplicationRunListeners管理---组合模式
		SpringApplicationRunListeners listeners = getRunListeners(args);
		...
		return context;
	}
在这里插入图片描述
在这里插入图片描述
  • 默认情况下,可以通过SPI机制获取到: 负责发布SpringBoot应用程序生命周期事件的监听器
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
        //事件派发器
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		//从SpringApplication拿到通过SPI加载得到的ApplicationListener集合
        for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

SpringApplicationRunListeners,EventPublishingRunListener和ApplicationListener三者关系如下:

在这里插入图片描述
在这里插入图片描述

我们继续回到SpringBoot run启动方法中来;

代码语言:javascript
复制
	public ConfigurableApplicationContext run(String... args) {
		..
		ConfigurableApplicationContext context = null;
		...
		//SPI加载SpringApplicationRunListener实现类,然后交给SpringApplicationRunListeners管理---组合模式
		SpringApplicationRunListeners listeners = getRunListeners(args);
		//发布SpringBoot应用程序启动事件
		listeners.starting();
		try {
		    //封装命令行参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			//环境上下文准备--配置类加载的地方
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			...
			context = createApplicationContext();
			...
			//这里我们需要关注一点: 此处会调用经过SPI加载得到的ApplicationContextInitializer的initialize方法
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			...
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		...
		listeners.running(context);
		return context;
	}
代码语言:javascript
复制
	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		//主要干的事情: 封装命令行参数为propertySource,加入环境上下文管理的属性源集合中
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		//发布环境上下文准备好的事件
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		...
		ConfigurationPropertySources.attach(environment);
		return environment;
	}
     
    //根据当前应用程序类型的不同,创建不同的环境上下文实现类
	private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
		}
	}

	protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
	    //封装命令行参数为propertySource,加入环境上下文管理的属性源集合中
		configurePropertySources(environment, args);
		// 从已有的属性源集合中更新激活profile的配置
		configureProfiles(environment, args);
	}		
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

监听环境上下文prepared事件
  • ConfigFileApplicationListener是如何加载配置文件的呢?
在这里插入图片描述
在这里插入图片描述
  • ConfigFileApplicationListener的onApplicationEvent方法如下
代码语言:javascript
复制
	@Override
	public void onApplicationEvent(ApplicationEvent event) {
	     //重点关注监听应用程序环境上下文准备好的这个事件处理逻辑
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		//一句话: 将SpringApplication中的defaultProperties对应的propertysource移动到属性源集合末尾
		//即: 具有最低优先级 --- 上面一开始添加过
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent(event);
		}
	}

	private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
	    //SPI机制加载所有EnvironmentPostProcessor
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		//ConfigFileApplicationListener自己本身也是一个EnvironmentPostProcessor
		postProcessors.add(this);
		AnnotationAwareOrderComparator.sort(postProcessors);
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
		}
	}
  • Apollo在这里也添加了一个自定义的EnvironmentPostProcessor
在这里插入图片描述
在这里插入图片描述
  • ConfigFileApplicationListener的postProcessEnvironment方法会调用addPropertySources方法
代码语言:javascript
复制
	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		addPropertySources(environment, application.getResourceLoader());
	}
    //addPropertySources方法中,会首先向环境上下中添加一个随机数属性源
	protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
		RandomValuePropertySource.addToEnvironment(environment);
		//创建一个Loader,调用其load方法,完成配置文件的加载
		new Loader(environment, resourceLoader).load();
	}

Loader加载配置文件
  • Loader如何完成配置文件的加载,以及如何封装配置文件为PropertySource,然后加载到Environment的PropertySources集合中呢?
代码语言:javascript
复制
		Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
			this.environment = environment;
			//search and parse==> environment内部的propertySources集合的delegate
            this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
            //加载配置的资源加载器
			this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(null);
            //通过SPI机制加载PropertySourceLoader
			this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
					getClass().getClassLoader());
		}
  • 默认提供的两个实现如下 – 分别负责加载.yml,.yaml和.properties结尾的配置文件
代码语言:javascript
复制
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
  • load方法源码如下
代码语言:javascript
复制
		void load() {
			FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
					(defaultProperties) -> {
						this.profiles = new LinkedList<>();
						this.processedProfiles = new LinkedList<>();
						this.activatedProfiles = false;
						this.loaded = new LinkedHashMap<>();
                        //初始化profiles--从已有的propertySource集合中解析得到激活的profile
						initializeProfiles();
						while (!this.profiles.isEmpty()) {
							Profile profile = this.profiles.poll();
                            //如果当前Profile是默认的,那么加入Environment的ActiveProfile集合
							if (isDefaultProfile(profile)) {
								addProfileToEnvironment(profile.getName());
							}
                           //加载配置文件,并通过getNegativeProfileFilter过滤进行过滤
                           //过滤规则: 
                           //1.如果传入的profile为null,那么过滤掉document的profiles不为空
                           //2.如果传入的profile不为null,那么过滤掉document的profile不等于传入的profile的
							load(profile, this::getPositiveProfileFilter,
                                    //将配置文件加载完毕后,回调此方法,用于将配置文件对应的PropertySource和Profile关系记录到loaded集合中
									addToLoaded(MutablePropertySources::addLast, false));
							//标记当前profile处理完毕
                            this.processedProfiles.add(profile);
						}
                        //加载配置文件,并通过getNegativeProfileFilter过滤进行过滤
                        //过滤规则: 1.如果传入的profile为null,那么过滤掉document的profiles不为空
                        //2.如果传入的profile不为null,那么过滤掉document的profile不等于传入的profile的
						load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
                        //将Loaded集合中记录的propertySource加入environment的propertySources集合中去
						addLoadedPropertySources();
                        //向environment中添加通过解析配置文件得到的activeProfiles
						applyActiveProfiles(defaultProperties);
					});
		}
  • initializeProfiles负责尝试从已有的propertySource集合中解析得到激活的profile
代码语言:javascript
复制
		private void initializeProfiles() {
			// The default profile for these purposes is represented as null. We add it
			// first so that it is processed first and has lowest priority.
			this.profiles.add(null);
			Binder binder = Binder.get(this.environment);
            //获取spring.profiles.active和spring.profiles.include
			Set<Profile> activatedViaProperty = getProfiles(binder, ACTIVE_PROFILES_PROPERTY);
			Set<Profile> includedViaProperty = getProfiles(binder, INCLUDE_PROFILES_PROPERTY);
            //获取Environment中已经存在的profile的,过滤掉了和上面两个重复的profile
			List<Profile> otherActiveProfiles = getOtherActiveProfiles(activatedViaProperty, includedViaProperty);
            //合并三个集合
			this.profiles.addAll(otherActiveProfiles);
			// Any pre-existing active profiles set via property sources (e.g.
			// System properties) take precedence over those added in config files.
			this.profiles.addAll(includedViaProperty);
			addActiveProfiles(activatedViaProperty);
            //如果我们没有通过命令行或者系统参数的形式配置spring.profiles.active
            //那么添加defaultProflie到profiles集合中
			if (this.profiles.size() == 1) { // only has null profile
				for (String defaultProfileName : this.environment.getDefaultProfiles()) {
					Profile defaultProfile = new Profile(defaultProfileName, true);
					this.profiles.add(defaultProfile);
				}
			}
		}
在这里插入图片描述
在这里插入图片描述
  • 核心load方法
代码语言:javascript
复制
		private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
			//默认springboot会去这几个路径下寻找配置文件
            //classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/
            getSearchLocations()
                .forEach((location) -> {
                //getSearchNames获取配置文件的前缀名,默认只有一个,为application
				boolean isDirectory = location.endsWith("/");
				Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;
				names.forEach((name) -> 
                              //location目录路径有了,配置文件名有了,还差个后缀名
                              //load方法拼接上后缀,然后去定位配置文件
                              load(location, name, profile, filterFactory, consumer));
			});
		}
  • load重载方法
代码语言:javascript
复制
 //location目录路径有了,配置文件名有了,还差个后缀名
//load方法拼接上后缀,然后去定位配置文件
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
				DocumentConsumer consumer) {
			...
			Set<String> processed = new HashSet<>();
            //默认情况下能够处理.properties后缀,.yml后缀或者是.yaml后缀
			for (PropertySourceLoader loader : this.propertySourceLoaders) {
				for (String fileExtension : loader.getFileExtensions()) {
					if (processed.add(fileExtension)) {
                        //配置文件完整路径=目录名+文件名+后缀名
						loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
								consumer);
					}
				}
			}
		}
  • loadForFileExtension定位加载配置文件
代码语言:javascript
复制
		private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
				Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
            //DocumentFilterFactory负责进行过滤
			DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
			DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
            //如果说profile不为null,那么还会尝试根据:目录名+文件名+"-"+profile+后缀去定位配置文件
			if (profile != null) {
				String profileSpecificFile = prefix + "-" + profile + fileExtension;
				load(loader, profileSpecificFile, profile, defaultFilter, consumer);
				load(loader, profileSpecificFile, profile, profileFilter, consumer);
				// Try profile specific sections in files we've already processed
				for (Profile processedProfile : this.processedProfiles) {
					if (processedProfile != null) {
						String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
						load(loader, previouslyLoaded, profile, profileFilter, consumer);
					}
				}
			}
			//根据 目录名+文件名+后缀去定位配置文件
			load(loader, prefix + fileExtension, profile, profileFilter, consumer);
		}
  • 真正加载配置文件的地方
代码语言:javascript
复制
		private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
				DocumentConsumer consumer) {
            //利用resourceLoader加载配置文件
			Resource[] resources = getResources(location);
			for (Resource resource : resources) {
				try {
                    //如果配置文件不存在则continue--省略配置文件不存在或者不符合要求的几种情况判断
                    ...
                    //配置文件存在--name作为propertySource的名字
					String name = "applicationConfig: [" + getLocationName(location, resource) + "]";
                    //加载配置文件信息
					List<Document> documents = loadDocuments(loader, name, resource);
					//如果集合为空,则continue
                    ...
                    // DocumentFilter进行过滤    
					List<Document> loaded = new ArrayList<>();
					for (Document document : documents) {
						if (filter.match(document)) {
                            //如果配置文件中指定了spring.profiles.active则进行记录
							addActiveProfiles(document.getActiveProfiles());
                            //如果配置文件中指定了spring.profiles.include则进行记录
							addIncludedProfiles(document.getIncludeProfiles());
							loaded.add(document);
						}
					}
					Collections.reverse(loaded);
					if (!loaded.isEmpty()) {
                        //调用回调接口,进行propertySource合并处理
                        //加入ConfigFileApplicationListener的Map<Profile, MutablePropertySources> loaded
                        //集合中
						loaded.forEach((document) -> consumer.accept(profile, document));
						...
					}
				}
				...
			}
		}
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  • 配置文件加载完毕后的效果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Apollo与SpringBoot整合原理

Apollo和Spring/Spring Boot集成的手段:在应用启动阶段,Apollo从远端获取配置,然后组装成PropertySource并插入到第一个即可,如下图所示:

在这里插入图片描述
在这里插入图片描述
  • apollo与spring整合的时候,是如何做到的呢?
    • 还记得上面看到的EnvironmentPostProcessor环境后置处理器嘛? 其实apollo在和Spring整合的时候就是添加了一个EnvironmentPostProcessor
在这里插入图片描述
在这里插入图片描述

Apollo在最开始的,是通过提供一个ApplicationContextInitializer,在prepareContext方法中会调用ApplicationContextInitializer的initialize方法,Apollo在此处完成命名空间配置拉取,然后包装成PropertySource加入环境上下文的属性源集合中。

在这里插入图片描述
在这里插入图片描述

但是问题是加载配置信息的时机比日志系统初始化晚,如果要让logbak-spring.xml文件能够正常读取Apollo的配置,则有两种方案可供选择:

  • 方案一:在ConfigFileApplicationListener 和LoggingApplicationListener之间插入一个监听器,用于初始化Apollo 配置信息;

这种方式在SpringBoot模式下想在这两者之间插入一个Listener 有点问题,Spring Boot 会将添加的Listener踢掉,目前还没弄清楚咋回事;另外这种插入方式和Listener的Order顺序有关,写死顺序不是很优雅;

  • 方案二:实现一个EnvironmentPostProcessor,供ConfigFileApplicationListener在加载好bootstrap.properties 和application.properties之后加载Apollo的配置信息

这种方式比较好些,添加一个EnvironmentPostProcessor实现,然后在spring.factories里面指定即可 注意:

  • 这种方式使得Apollo的加载顺序放到了日志系统加载之前,会导致Apollo的启动过程无法通过日志的方式输出(因为执行Apollo加载的时候,日志系统压根没有准备好呢!所以在Apollo代码中使用Slf4j的日志输出便没有任何内容)

详细参考github上提的pr: 增加EnvironmentPostProcessor处理,将Apollo配置加载提到初始化日志系统之前

  • ApolloApplicationContextInitializer–>postProcessEnvironment
代码语言:javascript
复制
  /**
   * 为了能够尽可能早的在spring加载日志系统之前加载apollo配置,ApolloApplicationContextInitializer的postProcessEnvironment可以在ConfigFileApplicationListener被成功调用后执行
   *
   * 此时的处理顺序会如下所示:
   * Load Bootstrap properties and application properties -----> load Apollo configuration properties ----> Initialize Logging systems
   */
  @Override
  public void postProcessEnvironment(ConfigurableEnvironment configurableEnvironment, SpringApplication springApplication) {

    //应始终首先初始化系统属性,例如 app.id --> 先从environment中取出app.id,apollo.meta等值
    //设置进System系统属性集合中 
    initializeSystemProperty(configurableEnvironment);
    //apollo.bootstrap.eagerLoad.enabled是否被设置为true
    Boolean eagerLoadEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_EAGER_LOAD_ENABLED, Boolean.class, false);

    //EnvironmentPostProcessor should not be triggered if you don't want Apollo Loading before Logging System Initialization
    if (!eagerLoadEnabled) {
      return;
    }
     //apollo.bootstrap.enabled属性为true才会启用apollo
    Boolean bootstrapEnabled = configurableEnvironment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_ENABLED, Boolean.class, false);

    if (bootstrapEnabled) {
      initialize(configurableEnvironment);
    }

  }
代码语言:javascript
复制
protected void initialize(ConfigurableEnvironment environment) {
//apollo会向environment中新增一个名为ApolloBootstrapPropertySources的propertysource
if(environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
      //already initialized
      return;
    }
    //获取我们配置文件中配置的apollo.bootstrap.namespaces配置,如果没配置,默认加载的命名空间为application
    String namespaces = environment.getProperty(PropertySourcesConstants.APOLLO_BOOTSTRAP_NAMESPACES, ConfigConsts.NAMESPACE_APPLICATION);
    logger.debug("Apollo bootstrap namespaces: {}", namespaces);
    List<String> namespaceList = NAMESPACE_SPLITTER.splitToList(namespaces);
    //创建一个名为ApolloBootstrapPropertySources的CompositePropertySource
    CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_BOOTSTRAP_PROPERTY_SOURCE_NAME);
    //依次拉取各个命名空间下的配置
    for (String namespace : namespaceList) {
      Config config = ConfigService.getConfig(namespace);
      //每个命名空间下的配置创建一个ConfigPropertySource接收
      composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
    }
   //将apollo对应的CompositePropertySource放在environment中PropertySources列表头部
    //说明具有最高优先级
    environment.getPropertySources().addFirst(composite);
  }
在这里插入图片描述
在这里插入图片描述

@Value注解的热更新原理

在这里插入图片描述
在这里插入图片描述
  • SpringValue的update方法
代码语言:javascript
复制
  public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {
    if (isField()) {
      injectField(newVal);
    } else {
      injectMethod(newVal);
    }
  }

  private void injectField(Object newVal) throws IllegalAccessException {
    Object bean = beanRef.get();
    if (bean == null) {
      return;
    }
    boolean accessible = field.isAccessible();
    field.setAccessible(true);
    field.set(bean, newVal);
    field.setAccessible(accessible);
  }

  private void injectMethod(Object newVal)
      throws InvocationTargetException, IllegalAccessException {
    Object bean = beanRef.get();
    if (bean == null) {
      return;
    }
    methodParameter.getMethod().invoke(bean, newVal);
  }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-05-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Apollo与SpringBoot整合原理深度剖析
  • @Value注解如何完成属性注入
  • application.yml等配置文件加载时机
    • 监听环境上下文prepared事件
    • Apollo与SpringBoot整合原理
    • @Value注解的热更新原理
    相关产品与服务
    容器服务
    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档