Spring创建容器总结
使用ClassPathXmlApplicationContext创建Spring容器时,做了以下几件事:
1、会先加载spring.properties配置文件;
2、然后获取Spring是否忽略spel的标识shouldIgnoreSpel;
3、创建标准环境StandardEnvironment;
4、创建占位符解析器;
5、创建占位符助手,用于占位符的替换;
6、将构造方法中的配置文件路径解析占位符之后设置给spring容器。
创建Spring容器对象
创建一个main方法,在Java的系统属性中设置一个键值对,用于配置文件路径中的占位符解析。使用ClassPathXmlApplicationContext创建一个spring容器,并指定需要加载的配置文件的路径,路径中包含占位符。
public static void main(String[] args) {
System.setProperty("xml", "/*.xml");
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("classpath*:${xml}");
}
在使用构造方法创建对象时,会先调用父类的无参构造方法。在创建ClassPathXmlApplicationContext容器时会先加载父类AbstractApplicationContext中的静态成员变量。该成员变量表示是否忽略spel,即是否spring是否支持spel,当值为false时,表示不会略spel,即spring支持spel。如果为true表示spring不支持spel。源码如下:
// 静态变量,加载该类的时候就会加载静态变量。
// SpringProperties类中有静态代码块。会通过类加载器的getResource获取URL,参数为spring.properties
private static final boolean shouldIgnoreSpel = SpringProperties.getFlag("spring.spel.ignore");
Spring加载spring.properties配置文件
上文中提到,在加载AbstractApplicationContext抽象类中的静态属性时,会调用SpringProperties的静态方法,在调用静态方法之前会先执行SpringProperties类中的静态代码块。静态代码块中会加载spring.properties配置文件,将文件中的内容以键值对的形式保存到SpringProperties中。源码如下:
static {
try {
// 获取SpringProperties类的类加载器
ClassLoader cl = SpringProperties.class.getClassLoader();
// 使用类加载器将spring.properties配置文件解析成URL
URL url = (cl != null ? cl.getResource(PROPERTIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResource(PROPERTIES_RESOURCE_LOCATION));
// 如果获取到spring.properties配置文件,会读取到配置文件中的属性并设置到spring的系统属性中
if (url != null) {
// 将配置文件加载到输入流中
try (InputStream is = url.openStream()) {
// 加载配置文件的键值对,添加到spring的系统属性中
localProperties.load(is);
}
}
}
catch (IOException ex) {
System.err.println("Could not load 'spring.properties' file from local classpath: " + ex);
}
}
Spring是否支持spel
Spring是否支持spel关系到是否解析占位符。Spring默认支持spel,即会正常解析占位符。我们可以通过以下三种方式修改spring的配置,让spring不再支持spel:
1、在Spring系统属性中设置键值对
SpringProperties.setProperty("spring.spel.ignore", "true");
2、在spring.properties配置文件中添加以下键值对
spring.spel.ignore=true
3、在Java的系统属性中设置键值对
System.setProperty("spring.spel.ignore", "true");
设置键值对时key是固定的,但是只有值设置为true时,上文中提到的shouldIgnoreSpel获取到的值为true,否则获取到的是false。如果同时采用以上三种方式设置,那么是第一种方式生效。因为使用第一种方式时,会先加载spring.properties配置文件,然后在调用set方法,会将原先的值覆盖掉。只有前两种方式都没有时,第三种方式才会生效。
Spring设置需要加载的配置文件路径
执行完以上逻辑之后,会执行ClassPathXmlApplicationContext的构造方法,设置配置文件的路径。调用setConfigLocations方法会将创建容器时传入的配置文件路径设置给容器的成员变量,该方法中会完成占位符的替换。构造方法源码如下:
// 核心构造函数,设置此应用上下文的配置文件的位置,并判断是否自动刷新上下文
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
// 将用父类的构造方法,设置父容器
super(parent);
//设置应用上下文的配置文件的位置,将配置文件的路径存放到configLocations字符串数组中
setConfigLocations(configLocations);
// 如果刷新表示为true,则会调用refresh()方法加载spring容器
if (refresh) {
refresh();
}
}
setConfigLocations会解析字符串中的占位符,源码如下:
// 将配置文件的路径放到configLocations 字符串数组中
public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
// 设置了几个配置文件,就创一个多长的字符串数组,用来存放配置文件的路径
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
//解析路径,将解析的路径存放到字符串数组中
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
Spring解析占位符
Spring解析占位符时,需要继续Spring的环境信息进行解析,因此会调用getEnvironment()获取环境信息,然后调用resolveRequiredPlaceholders方法解析字符串。
// 解析给定的路径,必要时用相应的环境属性值替换占位符。应用于配置位置。
protected String resolvePath(String path) {
// 获取环境,解决所需的占位符
return getEnvironment().resolveRequiredPlaceholders(path);
}
Spring创建标准环境StandardEnvironment
获取环境信息时,会判断当前环境对象是否为null,如果不为null,直接返回当前对象,如果为null,创建一个环境对象并赋值给成员变量(使用了设计模式中的单例模式,懒汉模式),最后返回环境对象,源码如下:
// 获取spring的环境信息,如果没有指定,获取到的时默认的环境
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
Spring创建占位符解析器
上文中提到创建环境对象会调用createEnvironment方法,该方法中会创建StandardEnvironment标准环境对象,进而会调用父类AbstractEnvironment的无参构造方法,进而对调用以下代码:
// 使用了模板方法设计模式。
// 给成员变量赋值,并调用子类重写的方法,对propertySources进行操作。
protected AbstractEnvironment(MutablePropertySources propertySources) {
// 给全局变量 可变属性源 赋值
this.propertySources = propertySources;
// 创建属性解析器:PropertySourcesPropertyResolver 属性源属性解析器
this.propertyResolver = createPropertyResolver(propertySources);
// 自定义属性源,此处回调子类重写的方法。子类通过重写该方法可以操作propertySources。spring标准环境StandardEnvironment重写了该方法
customizePropertySources(propertySources);
}
上文方法会调用createPropertyResolver方法创建属性源属性解析器,源码如下:
// 在创建环境时,需要创建属性解析器
protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {
return new PropertySourcesPropertyResolver(propertySources);
}
Spring创建占位符助手
上文中提到在使用getEnvironment方法获取到StandardEnvironment环境信息后会调用resolveRequiredPlaceholders方法解析占位符(StandardEnvironment类没有重写父类AbstractEnvironment中的resolveRequiredPlaceholders方法,因此调用的时AbstractEnvironment类中的方法),此方法中会调用解析器的resolveRequiredPlaceholders方法解析占位符,代码如下:
// 解析所需占位符
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
return this.propertyResolver.resolveRequiredPlaceholders(text);
}
在上述方法中调用解析器的resolveRequiredPlaceholders解析占位符。调用的是PropertySourcesPropertyResolver类中的resolveRequiredPlaceholders方法(继承自AbstractPropertyResolver),代码如下:
// 解析占位符
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
// 创建占位符助手
this.strictHelper = createPlaceholderHelper(false);
}
// 解析占位符
return doResolvePlaceholders(text, this.strictHelper);
}
解析占位符时,会先创建占位符助手,代码如下:
// 创建占位符助手
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
this.valueSeparator, ignoreUnresolvablePlaceholders);
}