Spring源码(小结):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);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我会尽力解释 Spring 容器的启动流程码。 1. 加载配置文件 Spring 容器启动时,首先会读取配置文件,通常是 `applicationContext.xml`。配置文件中定义了需要被 Spring 管理的 bean,以及它们的依赖关系等信息。 2. 创建 BeanDefinition Spring 容器会解析配置文件,创建对应的 BeanDefinition 对象。BeanDefinition 定义了 bean 的基本信息,如 bean 的类名、scope、依赖关系等等。 3. 注册 BeanDefinition 接下来,Spring 容器创建的 BeanDefinition 注册到 BeanDefinitionRegistry 中。BeanDefinitionRegistry 是一个接口,它定义了注册、查询、删除 BeanDefinition 的方法。 4. 实例化 Bean 接下来,Spring 容器将开始实例化 bean。Spring 容器使用反射创建 bean 的实例,然后根据配置文件中的信息对 bean 进行初始化。 5. 填充属性值 在 bean 实例化之后,Spring 容器会开始填充属性值。Spring 容器会根据配置文件中的信息,自动为 bean 填充属性值。这些属性可以是基本类型、引用类型、集合类型等等。 6. 调用 BeanPostProcessor 在填充完属性值之后,Spring 容器会调用 BeanPostProcessor 的方法。BeanPostProcessor 是一个接口,它定义了在 bean 实例化和初始化过程中的回调方法。通过实现 BeanPostProcessor,我们可以在 bean 实例化和初始化的过程中做一些自定义的操作。 7. 初始化 Bean 在填充完属性值和调用 BeanPostProcessor 之后,Spring 容器会调用 bean 的初始化方法。初始化方法可以是 init-method 指定的方法,也可以是实现了 InitializingBean 接口的 afterPropertiesSet() 方法。 8. 注册销毁方法 当 bean 的生命周期结束时,Spring 容器会调用它的销毁方法。销毁方法可以是 destroy-method 指定的方法,也可以是实现了 DisposableBean 接口的 destroy() 方法。 以上就是 Spring 容器启动流程的大概过程。其中,BeanDefinition、BeanPostProcessor、InitializingBean、DisposableBean 等接口和类都是 Spring 框架中提供的,它们为我们提供了更加灵活的配置和扩展方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值