0


【spring源码系列-03】xml配置文件启动spring时refresh的前置工作

Spring源码系列整体栏目


内容链接地址【一】spring源码整体概述https://blog.csdn.net/zhenghuishengq/article/details/130940885【二】通过refresh方法剖析IOC的整体流程https://blog.csdn.net/zhenghuishengq/article/details/131003428【三】xml配置文件启动spring时refresh的前置工作https://blog.csdn.net/zhenghuishengq/article/details/131066637

spring底层源码整体概述

一,xml配置文件启动spring时refresh的前置工作

前两篇大概的描述了一下springIoc的整体流程,接下来再对里面的细节进行分析。如下依旧是通过经典的xml的方式获取到上下文,并且在resources目录下配置一个spring.xml文件,这里推荐使用debug的方式,从上往下看

ApplicationContext ioc=newClassPathXmlApplicationContext("classpath:spring.xml");

在这里插入图片描述

进入这个获取上下文的构造方法之后,可以发现有调用了这个this方法

publicClassPathXmlApplicationContext(String configLocation)throwsBeansException{this(newString[]{configLocation},true,null);}

接下来在进入这个this方法,就是一个熟悉的方法,该方法在前两篇中有所提到。接下来重点就是对里面的前两个方法进行深究,弄清refresh的前置工作到底做了什么

publicClassPathXmlApplicationContext(String[] configLocations,boolean refresh,@NullableApplicationContext parent)throwsBeansException{super(parent);// 初始化父类 ,获得xml路径资源解析器setConfigLocations(configLocations);// 通过环境变量解析 xml路径if(refresh){refresh();// 这个方法时spring是最终要的一个方法,甚至体系整个ioc的声明周期}}

1,super(parent)

在该方法中,第一步就是初始化父类,后面很多需要使用的对象,就是在这一步被创建的,而里面的super继续调用自己的super,直到创建一个资源模式处理器,该 AbstractApplicationContext 相对来说比较重要,并且那个最重要的

refresh

方法就是在这个抽象类里面

publicAbstractApplicationContext(){//获取资源模式处理器this.resourcePatternResolver =getResourcePatternResolver();}

接下来就是查看这个具体的获取资源处理器的流程,里面的xml文件,或者其他的注解配置文件,都是能获取的资源,获取到资源之后就对资源进行一个解析操作

protectedResourcePatternResolvergetResourcePatternResolver(){returnnewPathMatchingResourcePatternResolver(this);}

接下来在查看这个 PathMatchingResourcePatternResolver 对象,可以发现里面就是获取资源对象加载器。并且里面还存在一个对象PathMatcher,用做于路径匹配

//用于模式匹配,默认使用的是 PathMatcherprivatePathMatcher pathMatcher =newAntPathMatcher();//获取资源加载器publicPathMatchingResourcePatternResolver(ResourceLoader resourceLoader){Assert.notNull(resourceLoader,"ResourceLoader must not be null");this.resourceLoader = resourceLoader;}

在这个ResourceLoader 类中,主要就是两个方法,一个是用于加载资源,一个是用于加载类加载器

//加载资源ResourcegetResource(String location);@Nullable//加载类加载器ClassLoadergetClassLoader();

在这个 AbstractApplicationContext 构造方法中,完成this获取一个资源解析器之后,接下来就是一个设置一个Parent的父类,当前springIOC中是没有父子容器的概念的,因此到后续的springMVC再进行分析

publicAbstractApplicationContext(@NullableApplicationContext parent){this();//springIOC中暂时没有父子容器概念,先跳过setParent(parent);}

因此这一整个步骤,都是为了初始化成员变量。而最主要的,就是初始化一个资源的解析器。

2,setConfigLocations()

在获取到这个资源解析器之后,接下来就是设置文件的路径。如在正常开发的springboot项目中,通过设置环境的的属性来表名是dev环境还是线上环境等。这个locations参数就是

new String[] {configLocation}
//参数可以是对象或者数组publicvoidsetConfigLocations(@NullableString... locations){if(locations !=null){Assert.noNullElements(locations,"Config locations must not be null");this.configLocations =newString[locations.length];for(int i =0; i < locations.length; i++){this.configLocations[i]=resolvePath(locations[i]).trim();}}else{this.configLocations =null;}}

2.1,获取系统属性和系统环境

在获取到外部传进来的文件路径之后,接下来会通过这个 resolvePath方法解析这个路径。而在解析这个路径时,需要通过系统环境变量来解析,如果环境变量为空,则创建一个标准的环境变量

protectedStringresolvePath(String path){//获取环境returngetEnvironment().resolveRequiredPlaceholders(path);}//如果获取的环境为空,则创建一个标准环境protectedConfigurableEnvironmentcreateEnvironment(){returnnewStandardEnvironment();}

而在这些环境中,存在一些spring环境变量的类型,分别是可忽视的,活跃的默认的等

publicstaticfinalString IGNORE_GETENV_PROPERTY_NAME ="spring.getenv.ignore";publicstaticfinalString ACTIVE_PROFILES_PROPERTY_NAME ="spring.profiles.active";publicstaticfinalString DEFAULT_PROFILES_PROPERTY_NAME ="spring.profiles.default";protectedstaticfinalString RESERVED_DEFAULT_PROFILE_NAME ="default";

同时在这个标准环境中,主要分为系统环境和系统属性等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vp1Jnj13-1686031945306)(img/1686014233433.png)]

//系统环境属性资源名称staticfinalString SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME ="systemEnvironment";//系统配置变量资源名称staticfinalString SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME ="systemProperties";

最后将全部的系统环境和系统属性一起加入到 propertySources 这个PropertySources集合中,该集合是在父类中实例化的,因此会作为一个全局共享的资源,其子类都能获取和访问

protectedvoidcustomizePropertySources(MutablePropertySources propertySources){
    propertySources.addLast(newPropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME,getSystemProperties()));
    propertySources.addLast(newSystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,getSystemEnvironment()));}

加入到集合的value值主要是系统的变量和系统的环境。

//获取系统的属性值getSystemProperties(){System.getProperties()};//获取系统的变量getSystemEnvironment(){System.getenv()};

在创建这个 StandardEnvironment() 标准的环境的时候,可以在父类的无参构造方法中打一个断点,可以发现此时会有两个属性值,就是上面的系统属性值和系统环境值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AhwsdtvS-1686031945307)(img/1685946585512.png)]

而在下面的propertySourceList的第一个值systemProperties中,已经加载了56个系统属性,比如说一些 jdk的版本,虚拟机的版本,操作系统的名称,当前用户的名称等等

在这里插入图片描述

在下面的propertySourceList的第二个值systemEnvironment中,也有49个值,比如说当前电脑的名称,使用的maven路径以及版本,java_home的路径,用户的用户名等等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JUlyXlED-1686031945308)(img/1685947066781.png)]

此时这些默认的环境对象和环境变量就全被获取。当然这些环境变量的数量也可能因为源码的版本不同个数也会不同。

2.2,解析系统环境和系统属性

又回到上面的第二步,此时环境变量值依旧获取,因此接下来就继续执行这个 resolveRequiredPlaceholders 方法

protectedStringresolvePath(String path){//获取环境returngetEnvironment().resolveRequiredPlaceholders(path);}

在resolveRequiredPlaceholders方法中,会获取到刚刚全部获取到的环境和属性,然后对这些环境和属性做一个解析操作。这里的话类似于一个责任链模式,系统环境要处理的会有对应的方法处理系统环境,系统属性要处理的会有对应的方法处理系统属性。下面这个是处理系统环境的方法

@OverridepublicStringresolveRequiredPlaceholders(String text)throwsIllegalArgumentException{//this.propertyResolver:全部的系统环境和系统属性returnthis.propertyResolver.resolveRequiredPlaceholders(text);}

处理完系统环境之后,会再次通过这个责任链模式,去处理对应的系统属性,下面这个是处理系统属性的方法

@OverridepublicStringresolveRequiredPlaceholders(String text)throwsIllegalArgumentException{if(this.strictHelper ==null){this.strictHelper =createPlaceholderHelper(false);}//解析工作returndoResolvePlaceholders(text,this.strictHelper);}

而在处理系统属性时,会有一个 createPlaceholderHelper 方法,类似于一个builder的工厂类

privatePropertyPlaceholderHelpercreatePlaceholderHelper(boolean ignoreUnresolvablePlaceholders){//前缀,后缀returnnewPropertyPlaceholderHelper(this.placeholderPrefix,this.placeholderSuffix,this.valueSeparator, ignoreUnresolvablePlaceholders);}

在获取到这个strictHelper 对象之后,接下来开始真正的进行解析工作

privateStringdoResolvePlaceholders(String text,PropertyPlaceholderHelper helper){return helper.replacePlaceholders(text,this::getPropertyAsRawString);}

再次进入这个replacePlaceholders 这个方法,可以发现里面会有一个重要的方法parseStringValue

publicStringreplacePlaceholders(String value,PlaceholderResolver placeholderResolver){Assert.notNull(value,"'value' must not be null");returnparseStringValue(value, placeholderResolver,newHashSet<>());}

接下来查看这个 parseStringValue 方法,首先会判断当前的value值中是否包含一个

$ 

的大括号,并且会递归的判断是否存在

$

的嵌套,判断完成之后,会对里面的值进行解析。

在这里插入图片描述

在获取完这个

     符之后,接着就是递归的循环遍历资源中的 
    
   
     k 
    
   
     e 
    
   
     y 
    
   
     值,将 
    
   
     < 
    
   
     c 
    
   
     o 
    
   
     d 
    
   
     e 
    
   
     > 
    
   
  
    符之后,接着就是递归的循环遍历资源中的key值,将<code> 
   
  
符之后,接着就是递归的循环遍历资源中的key值,将<code>{USERNAME} 对应的值进行一个替换操作。
String propVal = placeholderResolver.resolvePlaceholder(placeholder);

再次跟着debug断点走,可以发现会进入 PropertySourcesPropertyResolver 类的 getProperty 方法里面

在这里插入图片描述

里面进行循环的取值,将${}里面的值和系统属性或者系统变量的值进行匹配,如果匹配成功,则进行替换的操作

@Nullableprotected<T>TgetProperty(String key,Class<T> targetValueType,boolean resolveNestedPlaceholders){if(this.propertySources !=null){for(PropertySource<<?> propertySource :this.propertySources){//取值Object value = propertySource.getProperty(key);if(value !=null){//取值成功,则进行替换操作if(resolveNestedPlaceholders && value instanceofString){
                    value =resolveNestedPlaceholders((String) value);}logKeyFound(key, propertySource, value);//如果需要的换则进行值转换returnconvertValueIfNecessary(value, targetValueType);}}}returnnull;}

接下来再进入转换的convertValueIfNecessary 方法,如果不需要转换则直接返回,需要转换则转换

@Nullableprotected<T>TconvertValueIfNecessary(Object value,@NullableClass<T> targetType){if(targetType ==null){return(T) value;}ConversionService conversionServiceToUse =this.conversionService;if(conversionServiceToUse ==null){if(ClassUtils.isAssignableValue(targetType, value)){return(T) value;}
        conversionServiceToUse =DefaultConversionService.getSharedInstance();}//转换return conversionServiceToUse.convert(value, targetType);}

如已知刚刚获取到的系统环境变量中存在一个

USERNAME=‘PV’

,那么假设xml的文件名为

spring-$ {USERNAME}.xml

,那么结果这个解析器进行解析之后,就会将这个

${}

里面的值进行一个替换操作,会将这个文件名变成 spring-PV.xml 文件。如果存在$的嵌套,那么就会递归的进行一个判断和替换操作, 最终会将解析后的文件返回。

自此为止,属性值就全部加载和解析完成。此时所有的配置文件路径等,都添加在重要的类AbstractRefreshableConfigApplicationContextconfigLocations 的属性里面。

privateString[] configLocations;

除了刚刚举例,还有像一些jdbc的连接参数等等,其原理都是一样的,都是通过这种方式替换

"${jdbc.url}")"${jdbc.driverClassName}""${jdbc.username}""${jdbc.password}"

3,总结

也就是通过这个xml的方式作为配置文件,在调用refresh方法之前,主要就是做了两件事情:首先是初始化一个资源的解析器,随后是获取系统的属性和系统的环境变量,同时对配置文件的路径进行解析。至此为止,refresh需要准备的前戏工作结束。


本文转载自: https://blog.csdn.net/zhenghuishengq/article/details/131066637
版权归原作者 huisheng_qaq 所有, 如有侵权,请联系我们删除。

“【spring源码系列-03】xml配置文件启动spring时refresh的前置工作”的评论:

还没有评论