Spring之加载bean定义流程源码解析

在本文中,先对Spring加载和创建bean实例的流程跟着源代码走一遍,在后续的文章中再对所涉及的类的其他方法具体介绍。

		//这一步是加载指定的配置文件
		Resource resource = new ClassPathResource("bean.xml");
		//DefaultListableBeanFactory是BeanFactory的子类中最常用的一个类,实例化它作为我们IOC的容器
		DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
		//这个类是用来解析配置文件的,将配置文件中配置的bean加载到容器中
		XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
		//加载bean
		reader.loadBeanDefinitions(resource);
		//这一步是从容器中获取指定的bean,这一步也牵扯到了bean的实例化
		Person person = (Person) beanFactory.getBean("person");

下面,我们对上面的每一步都进行分析:

Resource resource = new ClassPathResource("bean.xml");

这一步是将我们的配置文件的路径,名称等属性放到一个类中,以便我们很轻松的获得它的各种信息。

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

实例化了一个工厂类,作为IOC容器,有关它的其它方法,在后续的文章中还会介绍

XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);

实例化了一个xml阅读器,这个类会将配置文件中所有的bean加载到beanFactory中。

reader.loadBeanDefinitions(resource);

阅读器开始加载bean,从这开始,我们跟进源码去探索他是怎么实现的:

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(new EncodedResource(resource));
	}

进去之后发现,它又调用了自身的方法,只不过将我们传进去的resource进行了包装,变成了EncodedResource,这只不过是记录了resource本身,以及它的字符集和编码信息。
继续:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		//断言我们传入的encodedResource不是空的
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		//打印日志
		if (logger.isInfoEnabled()) {
			logger.info("Loading XML bean definitions from " + encodedResource.getResource());
		}
		//这是一个放在当前线程中的set
		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		//Spring刚开始启动的时候肯定是空的,我们要创建一个set,并放到当前线程中
		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());
				}
				//从这开始,才真正的进行bean的加载
				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();
			}
		}
	}

我们发现,这个方法中逻辑也很简单,就是先从当前线程中获取到我们记录我们已经正在加载的配置文件的set集合,如果没有则创建,之后获取配置文件的输入流包装成inputSource和resource一起传到下一个方法中。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
			...
			//得到配置文件的document对象
			Document doc = doLoadDocument(inputSource, resource);
			//注册bean
			return registerBeanDefinitions(doc, resource);
			...
	}

这个方法中一个就做了两件事,但是这两件事都不简单,每一个逻辑都够我们研究很长时间的,,先是得到配置文件的document对象,有了它,我们就可以对它的们一个节点进行解析了,之后,就是要将bean注册到beanFactory中了。
第一步就是对xml对象的创建,我们主要来看第二步:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		//创建一个加载bean的解析器
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		//得到这个工厂中之前已经注册的bean的个数
		int countBefore = getRegistry().getBeanDefinitionCount();
		//开始注册bean
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		//返回本次注册bean的个数
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

其实到这我们也能发现,Spring把很多复杂的逻辑都给拆分成了一个个小的部分,显得很有条理,也便于我们阅读。我们继续跟进bean的注册:

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		logger.debug("Loading bean definitions");
		//获得document的根节点
		Element root = doc.getDocumentElement();
		//到这才真正开始注册
		doRegisterBeanDefinitions(root);
	}

这个方法也并没有做什么,我们发现Spring中真正执行逻辑的往往都是do开头的方法:

protected void doRegisterBeanDefinitions(Element root) {
		//在这会产生一个委托,由于在配置文件中可能会有嵌套的配置文件,所以会存在递归调用
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);
		...
		//前置处理
		preProcessXml(root);
		//解析bean
		parseBeanDefinitions(root, this.delegate);
		//后置处理
		postProcessXml(root);

		this.delegate = parent;
	}

在这个方法中提出了委托,一个delegate就代表了一个beans,是Spring配置文件中的根标签,在后面的解析中,也都是委托给他来做的,createDelegate()这个方法中就是对beans这个标签的解析。我们发现在这个方法中有两个方法叫做:preProcessXml和postProcessXml,点击去发现他们是空的,其实这交给用户定制去实现一些特殊需求的。继续跟进代码:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		//当前所要解析的配置文件是不是默认的命名空间
		if (delegate.isDefaultNamespace(root)) {
			//获得所有的孩子节点,一个孩子节点就是一个bean
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				//当前孩子节点是否是一个element
				if (node instanceof Element) {
					Element ele = (Element) node;
					//是否是默认的命名空间
					if (delegate.isDefaultNamespace(ele)) {
						parseDefaultElement(ele, delegate);
					}
					else {
						//单独处理
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			//单独处理
			delegate.parseCustomElement(root);
		}
	}

这个方法主要是对节点是否是默认的命名空间,分别进行解析,在这里,我们只要看一下对默认命名空间的解析:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		//对import标签解析
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		//对alias标签解析
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		//对bean标签解析
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	

这个方法是对不同的标签提供了不同的解析方法,我们先对bean标签的解析进行研究,以后还会对其他标签进行研究

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		//解析当前节点
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
			//解析完节点后还需要对他进行属性填充等工作
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// 注册bean
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			// 产生一个时间
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}

拨开云雾见天日,终于开始解析了:

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
		//首先是要获取当前bean的id和name属性
		String id = ele.getAttribute(ID_ATTRIBUTE);
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
		//获取别名
		List<String> aliases = new ArrayList<>();
		if (StringUtils.hasLength(nameAttr)) {
			String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			aliases.addAll(Arrays.asList(nameArr));
		}
		将bean的名字设置为id
		String beanName = id;
		if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
			beanName = aliases.remove(0);
			if (logger.isDebugEnabled()) {
				logger.debug("No XML 'id' specified - using '" + beanName +
						"' as bean name and " + aliases + " as aliases");
			}
		}

		if (containingBean == null) {
			checkNameUniqueness(beanName, aliases, ele);
		}
		//解析bean中的其他属性
		AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
		if (beanDefinition != null) {
			//如果当前bean定义中的beanName为空,那么就要为当前bean按照指定规则生成一个名字
			if (!StringUtils.hasText(beanName)) {
				try {
					if (containingBean != null) {
						beanName = BeanDefinitionReaderUtils.generateBeanName(
								beanDefinition, this.readerContext.getRegistry(), true);
					}
					else {
						beanName = this.readerContext.generateBeanName(beanDefinition);
						// Register an alias for the plain bean class name, if still possible,
						// if the generator returned the class name plus a suffix.
						// This is expected for Spring 1.2/2.0 backwards compatibility.
						String beanClassName = beanDefinition.getBeanClassName();
						if (beanClassName != null &&
								beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
								!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
							aliases.add(beanClassName);
						}
					}
					if (logger.isDebugEnabled()) {
						logger.debug("Neither XML 'id' nor 'name' specified - " +
								"using generated bean name [" + beanName + "]");
					}
				}
				catch (Exception ex) {
					error(ex.getMessage(), ele);
					return null;
				}
			}
			//将别名变成字符串数组
			String[] aliasesArray = StringUtils.toStringArray(aliases);
			//设置完参数返回
			return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
		}

		return null;
	}

这个方法中主要是对bean标签里面的各种属性进行解析,在配置文件中没有声明的就使用默认值,还没有进行对bean标签子节点的解析,在这里我们看见出现了一个新的类BeanDefinitionHolder,它其实就是对beanDefinition,beanName和aliasesArray的暂时封装,等到当前bean全部解析完之后会将他所封装的信息放大IOC容器中。

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
			Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {

		BeanDefinitionHolder finalDefinition = definitionHolder;

		//有些属性没有直接放到bean标签里面,而是放到了bean标签的下一级中来声明,对这些属性进行解析
		NamedNodeMap attributes = ele.getAttributes();
		for (int i = 0; i < attributes.getLength(); i++) {
			Node node = attributes.item(i);
			finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
		}

		// Decorate based on custom nested elements.
		NodeList children = ele.getChildNodes();
		for (int i = 0; i < children.getLength(); i++) {
			Node node = children.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
			}
		}
		return finalDefinition;
	}

在这个方法中主要是对bean标签里面那些不是默认命名空间的节点进行解析,并把值设置到bean定义里面。到这为止,一个完整的bean就被解析完了,下面就要把解析完的bean注册到IOC容器当中去:

public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		// 得到bean的名字
		String beanName = definitionHolder.getBeanName();
		//根据beanName注册bean
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		// 注册别名
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			//将别名一个一个的注册
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}

我们分开来看,先看一下注册bean的过程:

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {
		...
		//看一下这个bean是不是被注册过
		BeanDefinition oldBeanDefinition;
		oldBeanDefinition = this.beanDefinitionMap.get(beanName);
		if (oldBeanDefinition != null) {
			if (!isAllowBeanDefinitionOverriding()) {
				//这个bean已经被注册过了,并且不允许被覆盖,抛出异常
				...
			}
			...
			//覆盖注册
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
			//此时有其他bean开始创建,为了防止并发需要对当前IOC容器上锁,如果此时没有其他的bean正在创建,直接进行注册
			if (hasBeanCreationStarted()) {
				// Cannot modify startup-time collection elements anymore (for stable iteration)
				synchronized (this.beanDefinitionMap) {
					//将bean注册到容器中
					this.beanDefinitionMap.put(beanName, beanDefinition);
					//更新已经注册的bean的名字的列表
					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					if (this.manualSingletonNames.contains(beanName)) {
						Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
						updatedSingletons.remove(beanName);
						this.manualSingletonNames = updatedSingletons;
					}
				}
			}
			else {
				// Still in startup registration phase
				this.beanDefinitionMap.put(beanName, beanDefinition);
				this.beanDefinitionNames.add(beanName);
				this.manualSingletonNames.remove(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}

		if (oldBeanDefinition != null || containsSingleton(beanName)) {
			resetBeanDefinition(beanName);
		}
	}

这个方法首先检查有没有相同的beanName被注册并作出相应的处理,然后将beanName作为key,beanDefinition作为value放到IOC容器中,并且更新已经注册的bean的名字列表。当有其他bean正在创建的时候,为了防止并发,对IOC容器进行了上锁。

public void registerAlias(String name, String alias) {
		...
		if (alias.equals(name)) {
			this.aliasMap.remove(alias);
		}
		else {
			String registeredName = this.aliasMap.get(alias);
			if (registeredName != null) {
				if (registeredName.equals(name)) {
					// An existing alias - no need to re-register
					return;
				}
				if (!allowAliasOverriding()) {
					throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
							name + "': It is already registered for name '" + registeredName + "'.");
				}
			}
			//检查是否存在别名的循环。如果存在,抛出异常
			checkForAliasCircle(name, alias);
			//将别名添加到别名列表中
			this.aliasMap.put(alias, name);
		}
	}

好了,到这一个bean的加载就完成了,让我们来回顾一下:
一、指定要加载的配置文件
二、实例化一个IOC容器
三、实例化一个解析配置文件的阅读器,并设置好我们实例化的IOC容器
四、开始加载bean
1、封装resource,进入下一个方法进行处理
2、记录当前正在进行加载的resource
3、获得配置文件的输入流,开始解析
4、将xml文件装换成document对象,对每个节点解析
5、创建一个解析document对象的解析器,获得root节点,开始解析
6、正式解析之前和解析之后分别有一个前置处理和后置处理,交给用户对于特殊需求的自定义实现
7、遍历root的所有孩子节点,根据节点是否是默认的命名空间,选择不同的解析方法
8、根据不同的标签选择不同的解析方法,在本文中是以bean标签举例的
9、将bean标签的所有属性封装到beanDefinition中,并且将beanDefinition、beanName以及alias封装到beanDefinitionHolder中做进一步处理
10、解析完成,将别名、beanDefinition根据beanName注册到IOC容器中,等待实例创建
在下一篇文章中,我们将对Spring对bean实例的创建流程进行源码的解读。

猜你喜欢

转载自blog.csdn.net/m0_37343985/article/details/82819573