容器刷新前配置: https://www.cnblogs.com/xiaomaomao/p/14046219.html
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
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
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != 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) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
// 调用 AbstractApplicationContext 的 refresh 方法
wac.refresh();
}
wac 是 XmlWebApplicationContext 类型的, XmlWebApplicationContext 的继承结构如下:
这里的 wac 是 XmlWebApplicationContext 类型的,在它本类中没有找到 refresh() 方法,那么就往上找,结果在这张图的最上层接口 ConfigurableApplicationContext 中找到了 refresh() 方法,而ConfigurableApplicationContext 接口的实现类中只有 AbstractApplicationContext 这个抽象类实现了 refresh() 方法,所以我们最终会去到 AbstractApplicationContext 类中的 refresh()
找到 AbstractApplicationContext 类中的 refresh() 方法,我们主要看一下这个方法到底做了什么?
public void refresh() throws BeansException, IllegalStateException {
// 同步锁,如果 refresh() 方法还没有执行完成,这个时候你突然继续再来一次容器启动或者容器销毁的动作,那么就乱套了
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
// 告知子类刷新内部的 Bean Factory
// 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,并注册到 BeanFactory 中.
// 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了,
// 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map)
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
}
......
}
整个 refresh() 方法是 Spring IOC 的核心方法,可以看到 refresh 里面有很多的方法,但是我们这里探究的是刷新 BeanFactory 的操作,看看刷新 BeanFactory 的时候到底执行了哪一些的操作 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()
首先我们来到 obtainFreshBeanFactory() 方法
代码块一、obtainFreshBeanFactory()
// AbstractApplicationContext 类中的方法
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 1、刷新 BeanFactory,由 AbstractRefreshableApplicationContext 实现----(详细见代码块二)
refreshBeanFactory();
// 2、获取刚刚创建的 BeanFactory
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
// 3、返回 BeanFactory
return beanFactory;
}
代码块二、refreshBeanFactory()
// AbstractRefreshableApplicationContext 类中的方法
protected final void refreshBeanFactory() throws BeansException {
// 如果 ApplicationContext 中已经加载过了 BeanFactory ,销毁所有 Bean ,关闭 BeanFactory
// 注意:应用中 BeanFactory 本来就是可以多个的,这里可不是说应用全局是否有 BeanFactory,而是当前的
// ApplicationContext 是否持有 BeanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 2、创建 BeanFactory ,为什么那么多的 IOC 容器,单单要创建 DefaultListableBeanFactory 呢?这个下面会说到
DefaultListableBeanFactory beanFactory = createBeanFactory();
// 3、设置序列化 ID
beanFactory.setSerializationId(getId());
// 4、设置 BeanFactory 的两个属性,是否允许 bean 覆盖、是否允许循环引用
customizeBeanFactory(beanFactory);
// 5、加载 Bean 定义----(详细见代码块三)
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
createBeanFactory() 这个步骤返回的是一个 DefaultListableBeanFactory 类型,我们都知道 Spring 中有众多的 IOC 容器,为什么这里创建的是 DefaultListableBeanFactory 呢?我们可以通过下面这张继承关系图可以得出结论
通过上面这张图我们可以看出 ConfigurableListableBeanFactory 实现了第二层级的三个接口,并且它只有一个实现类 DefaultListableBeanFactory ,DefaultListableBeanFactory 这个实现类又通过继承 AbstractAutowireCapableBeanFactory 抽象类把整张图的功能都囊括了,这是其它的 BeanFactory 都做不到的,所以既然它的功能是最全的,这就是我们选择 DefaultListableBeanFactory 作为 BeanFactory 的实现类的原因.
代码块三、loadBeanDefinitions(beanFactory)
// XmlWebApplicationContext 中的方法
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 1、为指定的 beanFactory 创建一个 XmlBeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 2、设置环境信息
beanDefinitionReader.setEnvironment(getEnvironment());
// 3、将 XmlWebApplicationContext 赋值给 beanDefinitionReader 的 resourceLoader 属性
// XmlWebApplicationContext 实现了 ResourceLoader 接口
beanDefinitionReader.setResourceLoader(this);
// 4、设置实体解析器
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
// 5、空方法,留给子类去重写的方法
initBeanDefinitionReader(beanDefinitionReader);
// 6、加载 BeanDefinition(核心方法) ----(详情见代码块四)
loadBeanDefinitions(beanDefinitionReader);
}
代码块四、loadBeanDefinitions(beanDefinitionReader)
// XmlWebApplicationContext 中的方法
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
// 1、获取配置文件路径,如果 web.xml 中配置了 contextConfigLocation ,使用它的值作为 Spring 配置文件路径
// 如果没有配置 contextConfigLocation ,那么就使用 spring 默认的配置文件 /WEB/INF/application.xml
// (详情见代码块五)
String[] configLocations = getConfigLocations();
if (configLocations != null) {
// 2、遍历配置文件路径,因为配置文件的路径可以指定多个
for (String configLocation : configLocations) {
// 3、根据其中的一个配置文件路径加载 BeanDefinitions ----(详情见代码块六)
reader.loadBeanDefinitions(configLocation);
}
}
}
代码块五、getConfigLocations()
// AbstractRefreshableWebApplicationContext 类中的方法
public String[] getConfigLocations() {
// 1、调用父类方法
return super.getConfigLocations();
}
// AbstractRefreshableConfigApplicationContext 类中的方法
protected String[] getConfigLocations() {
// 1、如果 web.xml 中配置了 contextConfigLocation ,使用该参数对应的值作为 spring 的配置文件
// 2、如果没有配置,则使用 getDefaultConfigLocations()
return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
}
// XmlWebApplicationContext 类中的方法
protected String[] getDefaultConfigLocations() {
if (getNamespace() != null) {
return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
}
else {
// spring 默认的配置文件路径 /WEB/INF/application.xml
return new String[] {DEFAULT_CONFIG_LOCATION};
}
}
代码块六、loadBeanDefinitions(configLocation)
// AbstractBeanDefinitionReader 类中的方法
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
// 1、调用本类中两个参数的构造方法,第二个参数值为 null ---- (详情见代码块七)
return loadBeanDefinitions(location, null);
}
代码块七、loadBeanDefinitions(location, null)
// AbstractBeanDefinitionReader 类中的方法
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
// 1、获取 ResourceLoader ,我们这里是 XmlWebApplicationContext
ResourceLoader resourceLoader = getResourceLoader();
// 2、如果类加载器为空,抛出异常
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
// 3、判断 resourceLoader 是否为 ResourcePatternResolver 的实例
if (resourceLoader instanceof ResourcePatternResolver) {
try {
// 3.1、将 String 类型的配置文件名根据路径、前后缀等进行匹配获取到符合条件的配置文件,然后转成 Resource 类型的数组
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 3.2、根据 resources 加载 BeanDefinitions ---- (详情见代码块八)
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
// 3.3、返回加载 BeanDefinition 的个数
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// 4、通过绝对路径来加载资源,因为是绝对路径,只能加载一个配置文件
Resource resource = resourceLoader.getResource(location);
// 5、通过绝对路径来加载 BeanDefinitions
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
代码块八、loadBeanDefinitions(resources)
// AbstractBeanDefinitionReader 类中的方法
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
// 1、根据单个 Spring 配置文件加载 BeanDefinitions ---- (详情见代码块九)
counter += loadBeanDefinitions(resource);
}
// 2、返回最后总共加载的 BeanDefinition
return counter;
}
代码块九、loadBeanDefinitions(resource)
// XmlBeanDefinitionReader 类中的方法
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
// 1、加载 BeanDefinition ---- (详情见代码块十)
return loadBeanDefinitions(new EncodedResource(resource));
}
代码块十、loadBeanDefinitions(new EncodedResource(resource))
// XmlBeanDefinitionReader 类中的方法
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 1、核心方方法,执行加载 BeanDefiniton ----(详情见代码块十一)
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
代码块十一、doLoadBeanDefinitions(inputSource, encodedResource.getResource())
// XmlBeanDefinitionReader 类中的方法
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 1、根据当前的一个 Spring 的配置文件创建 Document 对象
Document doc = doLoadDocument(inputSource, resource);
// 2、注册 BeanDefinition ---- (详情见代码块十二)
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
代码块十二、registerBeanDefinitions(doc, resource)
// XmlBeanDefinitionReader 类中的方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 1、创建 BeanDefinitionDocumentReader 对象
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 2、获取之前注册的 BeanDefinition 的个数
int countBefore = getRegistry().getBeanDefinitionCount();
// 3、注册 BeanDefinition ---- (详情见代码块十三)
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 4、返回本次注册的 BeanDefinition 总数目
return getRegistry().getBeanDefinitionCount() - countBefore;
}
代码块十三、registerBeanDefinitions(doc, createReaderContext(resource))
// DefaultBeanDefinitionDocumentReader 类中的方法
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
// 1、获取 Document 元素的根节点, Spring 配置文件的根节点一般都是 beans
Element root = doc.getDocumentElement();
// 2、根据根节点注册 BeanDefinitions ---- (详情见代码块十四)
doRegisterBeanDefinitions(root);
}
代码块十四、doRegisterBeanDefinitions(root)
protected void doRegisterBeanDefinitions(Element root) {
// 1、
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
// 1、校验 root 节点的命名空间是否为默认的命名空间(Spring 默认命名空间http://www.springframework.org/schema/beans)
if (this.delegate.isDefaultNamespace(root)) {
// 2、获取 Profile 属性
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// 3、校验当前节点的 profile 是否符合当前环境定义的,,如果不是则直接跳过,不解析该节点下的内容
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
// 4、钩子方法,留给子类实现
preProcessXml(root);
// 5、解析 BeanDefinitions ---- (详情见代码块十五)
parseBeanDefinitions(root, this.delegate);
// 6、钩子方法,留给子类实现
postProcessXml(root);
this.delegate = parent;
}
profile 属性主要用于多环境开发,用于切换不同的环境,例如下图:
我们可以在配置文件中同时写上多套配置来适用于 dev 环境、sit 环境、uat 环境,这样可以方便的进行切换开发、部署环境,最常用的就是更换不同的数据库.具体使用哪个环境在 web.xml 中通过参数 spring.profiles.active 来配置,如下图
代码块十五、parseBeanDefinitions(root, this.delegate)
// DefaultBeanDefinitionDocumentReader 类中的方法 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { // 1、解析默认名称空间下面的默认元素 parseDefaultElement(ele, delegate); } else { // 2、解析默认名称空间下面的自定义元素 delegate.parseCustomElement(ele); } } } } else { // 3、解析自定义命名空间下的元素 delegate.parseCustomElement(root); } }
先看一张图,这里面的不带前缀的就是默认的命名空间,对应的是: xmlns="http://www.springframework.org/schema/beans ,该空间中默认的元素有 import、alias、beans、description、import
默认空间下的自定义元素我们经常用到的主要有 <context:component-scan base-package="com.bocom"> 、<tx:annotation-driven/> 等,不过要使用这些标签需要引入对应的名称空间例如 context、tx
自定义的命名空间就是自己定义的名称空间