有没有code能改xml内容_Spring源码解析-applicationContext.xml加载和bean的注册

applicationContext文件加载和bean注册流程

​ Spring对于从事Java开发的boy来说,再熟悉不过了,对于我们这个牛逼的框架的介绍就不在这里复述了,Spring这个大杂烩,怎么去使用怎么去配置,各种百度谷歌都能查到很多大牛教程,但是,当我们按着教程一步步的把spring的开发框架搭建起来的时候,有没有一种想搞明白spring的冲动,万事开头难,就要从开头开始,而我认为spring开头就是如何加载配置文件,并初始化配置文件里面的bean当然也包括了我们用注解Service、Component等注解注解的bean,spring在容器启动的时候就要去加载这些内容,然后统一管理这些bean(统一管理的是他们的bean definition),这也就是spring的一个重要概念bean的容器。

​ applicationContext.xml到底是如何加载的呢?我把他简化成以下流程,当然了每个环节里Spring的实现都是错综复杂的,也是很佩服写Spring的大神。

29c244a4038fb90c617ddeb298638005.png

Spring初始化

​ 当我们初学Spring的教程的时候,教程里面肯定会有这样的一步操作,就是新建一个applicationContext.xml文件,当然了这是Spring里必须要有的一个文件,在这个文件里面我们可以进行bean的配置等等工作,让Spring来管理我们的Bean。然后,这个文件放在哪里也是个比较讲究的事情,可能对于初学者来说可额能会往WEB-INF文件夹一放就了事了,确实这样是可以的,因为Spring默认的位置就是这个,但是我们一般不这么做,一般会把这个文件放在resource里面,那这样子做的话,你就要指定位置,让Spring知道你这个文件的位置,这就有了下面一段代码,我们的Spring项目都会在web.xml配置这样的代码:

contextConfigLocationclasspath:applicationContext.xml

那问题来了,当项目启动的时候,spring是怎么去初始化应用的上下文的呢?答案就在类ContextLoader.java里面。当Tomcat启动时候会调用该类里面的一个方法public WebApplicationContext initWebApplicationContext(ServletContext servletContext),这个方法主要完成,根据我们在web.xml里面配置的contextConfigLocation初始化spring的web的应用上下文。具体看下改方法的实现(非完整代码,PS:由于太长了):

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {...... this.context = createWebApplicationContext(servletContext);//主要代码,创建web应用上下文  ...... configureAndRefreshWebApplicationContext(cwac, servletContext);//配置参数并调用初始化方法 ...... }

在这个方法里面有两句重要代码,第一句createWebApplicationContext(servletContext),这个会根据你配置的contextClass创建一个WebApplicationContext对象,但是我们一般不会配置这个参数,所以Spring默认会创建一个XMLWebApplicationContext对象,而这个就是后续操作的的重要对象,然后接下来一句重要代码configureAndRefreshWebApplicationContext(cwac, servletContext)这个就会去读取我们在web.xml里面配置的参数并set到变量里头去,这样Spring就能找到我们项目的applicationContext.xml文件了,到底如何找到下面会讲。接下来我们来看下configureAndRefreshWebApplicationContext方法的实现如下:

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 informationString 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 #refreshConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(sc, null);}customizeContext(sc, wac);wac.refresh();}

在这个方法中我们只要关注两个地方,第一个:

String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);if (configLocationParam != null) {wac.setConfigLocation(configLocationParam);}

这块代码块就是,讲我们配置在web.xml里面的参数set到我们的变量中去。第二个地方就是:

wac.refresh();

调用这个执行后续的加载文件操作等后续操作。

Spring是如何找到applicationContext.xml文件

​ 其实,从refresh到Spring里去查找配置文件路径之间,有很多步骤,这些也都要花点时间去理解的,在这里不展开讲,我们只要知道,XmlWebApplicationContext会委托给XmlBeanDefinitionReader类去解析配置文件,在XmlWebApplicationContext类里面有个方法loadBeanDefinitions如下:

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {String[] configLocations = getConfigLocations();if (configLocations != null) {for (String configLocation : configLocations) {reader.loadBeanDefinitions(configLocation);}}}

欢迎工作一到五年的Java工程师朋友们加入Java程序员开发: 721575865

群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

该方法就是将一个个的配置文件委托给XmlBeanDefinitionReader去解析配置文件,但是解析之前有句代码String[] configLocations = getConfigLocations();这个就是查找我们的配置的文件的方法,

protected String[] getConfigLocations() {return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());}

实现很简单,就是我们有配置该位置地址就会去读我们配置的路径,否则就会去读默认的配置文件路径,这就是开篇说到的要是没配置路径也能读取到配置文件,前提就是要跟Spring默认定义好的文件路径及文件名保持一致才行。getDefaultConfigLocations函数的实现也很简单:

/** Default config location for the root context */public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";/** Default prefix for building a config location for a namespace */public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";/** Default suffix for building a config location for a namespace */public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";protected String[] getDefaultConfigLocations() {if (getNamespace() != null) {return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};}else {return new String[] {DEFAULT_CONFIG_LOCATION};}}

如果配置了namespace就会去找这个名字的xml配置文件,如果没有配置就去找默认的配置文件。所以不管如何,这个配置文件是必须在spring项目中的。至此,配置文件基本将完,接下来就是重头戏了,就是解析xml以及xml里面的节点,并注册到spring的bean容器中去。

将xml文件转成Document处理对象

如何将xml转成Document对象,这个也是很复杂的操作,首先将resource读取InputStream流,在将InputStream流包装成InputSource对象,在处理成Document对象,直接上代码:

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 currentResources = this.resourcesCurrentlyBeingLoaded.get();if (currentResources == null) {currentResources = new HashSet(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());}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());方法去了,该方法就是生成Doucument对象的,然后就是解析具体的节点了,部分源码如下:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {Document doc = doLoadDocument(inputSource, resource);//这就是解析成Document对象的操作return registerBeanDefinitions(doc, resource);......}

解析Document不展开讲了,不是本篇的重点,重点是下面的,spring如何解析xml文件的bean及注解的bean然后注册到容器中去,registerBeanDefinitions(doc, resource)是下面的重点。

解析Document里面的节点

XmlBeanDfinitionReader本身又不是直接取解析document的,他是委托给了DefaultBeanDefinitionDocumentReader类去实现,源代码中,会去创建DefaultBeanDefinitionDocumentReader对象实例,然后调用实例的注册方法,代码如下:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();int countBefore = getRegistry().getBeanDefinitionCount();documentReader.registerBeanDefinitions(doc, createReaderContext(resource));return getRegistry().getBeanDefinitionCount() - countBefore;}

首先,我们必须知道,spring的xml文件里面有两种类型的节点,一种是默认节点,相对于默认节点之外的节点统称自定义节点,这可以从源码里面知道,而默认节点有以下几个:beans、import、alias、bean这几个节点是默认节点,而相对于这几个节点之外的都是默认节点,applicationContext里面有几个自定义节点,如下:property-placeholder、property-override、annotation-config、component-scan、load-time-weaver、spring-configured、mbean-export、mbean-server,这里面常见的有component-scan等,为什么spring要分成默认和自定义节点呢,是因为自定义节点都有特定的业务,比如component-scan,他是去扫描程序包,加载用注解定义的bean,例如开发中的service等bean,所以这些自定义节点都配备了解析器,这些解析器预先初始化好的,解析到什么节点就去获取相应的解析器去处理相应的业务,自定义节点解析器配置如下:

@Overridepublic void init() {registerBeanDefinitionParser("property-placeholder
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值