2、容器的刷新

    上一节分析了父容器(ClasspathXmlApplicationContext)的加载,其加载的配置文件可以在web.xml中配置,但是并没有仔细的去研究父容器的创建的过程,那是因为父容器的创建与子容器的创建基本一致。本节将分析ApplicationContext与BeanFactory的刷新过程。

2.1 配置文件的解析

    context创建后,接下来就需要准备好配置文件,以供后面解析配置做准备。

//(0) 配置和刷新context
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        //底层使用了JDK提供的类名+@+hashCode地址作为唯一id,在调用容器构造器的时候生成
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			// The application context id is still set to its original default value
			// -> assign a more useful id based on available information
			//通过在web.xml中配置contextId来修改默认的id
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			}
			else {
				// Generate default id...
				//如果web.xml中未提供,生成与web相关的id
				//一般情况下就是org.springframework.web.context.WebApplicationContext: + sc.getContextPath())
				//目前这个id,我看见在构建容器关联的BeanFactory的时候被使用到,用于设置BeanFactory的序列化id
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}

		wac.setServletContext(sc);
//从web.xml中获取配置文件路径
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
//设置配置文件路径,这个容器是实现了ConfigurableWebApplicationContext接口,还有PropertyResolver接口,会对这个配置路径进行占位符的解析,目前能够获取到的占位符属性为System.getProperties(),System.getEnv(),其他的ServletContext的属性,ServletConfig的属性都为存根对象,直接返回null
			wac.setConfigLocation(configLocationParam);
		}

		// The wac environment's #initPropertySources will be called in any case when the context
		// is refreshed; do it eagerly here to ensure servlet property sources are in place for
		// use in any post-processing or initialization that occurs below prior to #refresh
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
//替换创建ConfigurableEnvironment时设置的存根占位符
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
//调用ApplicationContextInitializer
		customizeContext(sc, wac);
//(1)刷新容器
		wac.refresh();
	}

    上面给创建的context设置唯一的id之后,从web.xml中读取了配置文件的路径,并将配置文件路径设置给context,看似是简单的set方法,实际上spring做了其他的事情,那么就是对配置文件路径进行占位符的解析,替换完占位符才会设置给context,解析完配置文件路径之后,会调用实现了ApplicationContextInitializer的初始化方法,最后就是刷新容器。     这里还有一个值得注意的是,在设置配置文件路径的过程中,为了解析占位符,spring会创建一个StandardServletEnvironment实例,这个实例具有提供属性资源和解析占位符的能力,接下来,我们来看看spring是如果解析占位符的。

public void org.springframework.context.support.AbstractRefreshableConfigApplicationContext.setConfigLocations(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++) {
			    //(1)解析占位符
		 this.configLocations[i] = resolvePath(locations[i]).trim();
			}
		}
		else {
			this.configLocations = null;
		}
	}
	
	//(1)解析占位符,那解析后的占位符的属性值,从哪里找呢?所以这个时候我们需要创建一个环境实例,用于提供属性值,由于当前context是实现了WebApplicationContext接口,继而它的实现方法在AbstractRefreshableWebApplicationContext中,所以它所创建的环境变量当然会是与web相关的StandardServletEnvironment
	
	protected ConfigurableEnvironment createEnvironment() {
		return new StandardServletEnvironment();
	}

    我们来看看StandardServletEnvironment的类图

在这里插入图片描述

PropertyResolver(定义根据属性key获取属性值,定义解析占位符功能)

ConfigurablePropertyResolver(定义获取ConversionService的能力,用于进行属性值的转换,ConversionServic,提供转换能力,ConverterRegistry,注册转换器)

Environment(定义获取默认激活配置文件profile的能力)

ConfigurableEnvironment(设置激活配置文件,以及获取配置文件的能力)

AbstractEnvironment(实现了解析占位符的能力,持有PropertySourcesPropertyResolver(用于解析占位符),MutablePropertySources(提供属性值,内部采用组合模式)实例对象)

StandardEnvironment(customizePropertySources)
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
//内部使用System.getProperty()
   propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
//内部使用System.getEnv()
   propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
StandardServletEnvironment(customizePropertySources)
protected void customizePropertySources(MutablePropertySources propertySources) {
//设置servletConfigInitParams存根,其实就是起占位符的能力  
 propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
//servletContextInitParams
   propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
//jndi占位符jndiProperties
   if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
      propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
   }
//这个就是上面那个方法
   super.customizePropertySources(propertySources);
}

    从上面的继承结构我们大致了解了StandardServletEnvironment具有提供属性值和解析替换占位符的能力,那么它的属性资源从哪里来呢?其中有部分属性资源在构造它的实例的时候就会默认添加,具体代码如下:

protected void org.springframework.web.context.support.StandardServletEnvironment.customizePropertySources(MutablePropertySources propertySources) {
        //添加servletConfig存根对象,不提供属性值,调用时返回null(所谓存根对象,相当于占位符,后期会进行替换,为啥要占着茅坑不拉屎呢?你想象一下停车位就知道了,你要放个牌子,告诉别人这是我的,如果你不放就会被人霸占。)
		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));
		}
		//调用父类的customizePropertySources方法super.customizePropertySources(propertySources);这里我将父类的方法内联进来了
		//添加系统属性变量
		propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		
		//添加系统环境变量
		propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
		
		
	}

    以上通过MutablePropertySources添加了多个属性源,当所需要的属性源准备就绪之后就可以进行占位符的解析了

protected String resolvePath(String path) {
        //解析占位符
		return getEnvironment().resolveRequiredPlaceholders(path);
	}
	
	
	
	public String 	org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
	    //创建占位符解析帮助类,这个类主要用于占位符的解析,而当前实例是PropertySourcesPropertyResolver类的实例,这个类主要负责属性值的获取
		if (this.strictHelper == null) {
			this.strictHelper = createPlaceholderHelper(false);
		}
		//解析占位符,将解析后的占位符到属性资源中获取属性值,然后替换之后返回
		return doResolvePlaceholders(text, this.strictHelper);
	}
	
	
	//解析占位符
	protected String parseStringValue(
			String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

		StringBuilder result = new StringBuilder(strVal);
        //获取${开始位置
		int startIndex = strVal.indexOf(this.placeholderPrefix);
		while (startIndex != -1) {
		    //获取最终与${配对的},因为可能存在这样的${${spring.profil.action}:dev}
		    //这里的${应当匹配:dev后面这个}
			int endIndex = findPlaceholderEndIndex(result, startIndex);
			if (endIndex != -1) {
				String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
				String originalPlaceholder = placeholder;
				//不允许循环引用,否则一直递归,导致Stack Overflow
				if (!visitedPlaceholders.add(originalPlaceholder)) {
					throw new IllegalArgumentException(
							"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
				}
				// Recursive invocation, parsing placeholders contained in the placeholder key.
				//递归解析占位符
				placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
				// Now obtain the value for the fully resolved key...
				//获取占位符的值
				String propVal = placeholderResolver.resolvePlaceholder(placeholder);
				if (propVal == null && this.valueSeparator != null) {
				   //如果没有获取到占位符,那么获取默认值,比如${${spring.profil.action}:dev},默认值是dev
					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;
						}
					}
				}
				if (propVal != null) {
					// Recursive invocation, parsing placeholders contained in the
					// previously resolved placeholder value.
					//递归解析属性值的占位符
					propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
					result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
					if (logger.isTraceEnabled()) {
						logger.trace("Resolved placeholder '" + placeholder + "'");
					}
					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());
				}
				else {
					throw new IllegalArgumentException("Could not resolve placeholder '" +
							placeholder + "'" + " in string value \"" + strVal + "\"");
				}
				visitedPlaceholders.remove(originalPlaceholder);
			}
			else {
				startIndex = -1;
			}
		}

		return result.toString();
	}
	
	

    下面是解析占位符的序列图

在这里插入图片描述

2.2 context的刷新

public void org.springframework.context.support.AbstractApplicationContext.refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
//(2)设置context启动时间,设置激活标记,设置是否关闭标记,并且初始化资源,这里会提供初始化属性资源的机会,并对属性资源的校验机会
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
//(3)创建context关联的BeanFactory,这个创建的是DefaultListableBeanFactory,刷新容器,加载配置文件

	ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			。。。。。。
	}



//(2)
protected void org.springframework.context.support.AbstractApplicationContext.prepareRefresh() {
//设置启动时间
		this.startupDate = System.currentTimeMillis();
//设置关闭flag
		this.closed.set(false);
//设置激活flag
		this.active.set(true);

		if (logger.isInfoEnabled()) {
			logger.info("Refreshing " + this);
		}

		// Initialize any placeholder property sources in the context environment
//我们可以继承这个类,然后设置自己的propertSource,自己重新设置配置文件位置,甚至解析占位符。纵观以前的运行流程,我们可以更改设置父容器的能力,id,环境等等信息,甚至我们后面可以修改创建BeanFactory的方法等方式。
		initPropertySources();

		// Validate that all properties marked as required are resolvable
		// see ConfigurablePropertyResolver#setRequiredProperties
//校验一些必填属性
		getEnvironment().validateRequiredProperties();

		// Allow for the collection of early ApplicationEvents,
		// to be published once the multicaster is available...
//这里可以设置饿汉事件,一旦事件广播可用,就会触发对应的事件
		this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
	}


//(3)这个方法内部调用了这个方法
protected final void org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory() throws BeansException {
//如果已经存在BeanFactory,那么就进行销毁,主要用于重新启动容器的时候
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
//创建BeanFactory
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
//设置BeanFactory是否允许BeanDefinition的覆盖,是否允许循环依赖
			customizeBeanFactory(beanFactory);
//(4)创建XmlBeanDefinitionReader,设置BeanFactory到XmlBeanDefinitionReader等,准备加载BeanDefinition
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}


//(4)
protected void org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
//设置环境属性,用于配置文件中占位符的解析
		beanDefinitionReader.setEnvironment(getEnvironment());
//设置配置资源加载器,由于context实现了ResourceLoader接口,context是有加载资源的能力的
		beanDefinitionReader.setResourceLoader(this);
//解析器,用于校验配置文件的正确性
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
//钩子函数,提供给子类实现
		initBeanDefinitionReader(beanDefinitionReader);
//使用reader加载解析配置文件
		loadBeanDefinitions(beanDefinitionReader);
	}

    上面这段代码上面的注释非常明确,这里不多赘述,主要是看看DefaultListableBeanFactory,context应用了这个BeanFactory的实例,那么它具有什么样的能力,我们来看下它的类图

在这里插入图片描述

AliasRegistry(提供别名注册,移除的功能)

BeanDefinitionRegistry(提供BeanDefinition注册能力)

SimpleAliasRegistry(提供别名注册,移除的实现能力)

DefaultSingletonBeanRegistry(提供单例bean的注册,获取)

FactoryBeanRegistrySupport(定义如何获取工厂bean与注册工厂bean到缓存中)

AbstractBeanFactory(主要提供获取bean的模板方法(createBean))

AbstractAutowireCapableBeanFactory(具有自动装配能力以及BeanFactoryPostProccess扩展能力)

SingletonBeanRegistry(注册单例bean,获取单例bean)

BeanFactory(定义获取bean的能力)

HierarchicalBeanFactory(定义获取父BeanFactory的能力)

ConfigurableBeanFactory(提供配置功能,定义获取转换器的能力)

AutowireCapableBeanFactory(定义自动装配的功能)

ListableBeanFactory(定义可枚举bean的能力)

ConfigurableListableBeanFactory(定义了提前初始化非懒加载bean)

DefaultListableBeanFactory(提供具体的枚举bean的能力)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值