容器刷新前配置: 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
自定义的命名空间就是自己定义的名称空间
参考:https://joonwhee.blog.csdn.net/article/details/86563620