Spring容器启动之扫描加载类实现原理

整理代码过程中极为痛苦,我要坚持。

你的坚持,终将美好

spring容器启动会执行AbstractApplicationContext#refresh()方法,该方法是spring启动核心方法,下面主要整理下spring容器在启动时,是如何将项目里面的类,扫描到并注册到spring容器中。

先看下refresh()方法概要时序图

将容器的储存空间映射 容器文件映射_将容器的储存空间映射


本章主要讲述的是1.2步骤中,扫描并注册Bean的过程,先看下1.2源码

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		/**
		 * 注意下面这个方法,该方法就是扫描项目中的Bean,并将Bean注入到BeanDefinition中。
		 */
		refreshBeanFactory();
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		return beanFactory;
	}

接下来进入refreshBeanFactory方法源码

@Override
	protected final void refreshBeanFactory() throws BeansException {
		/**
		 * 查看是否启动的临时容器,存在就销毁关闭容器。为下面创建容器做准备。
		 */
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			//创建容器并设置容器标示
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			/**
			 * 定制容器,里面是设置是否允许BeanDefinition重写和循环引用。默认false
			 */
			customizeBeanFactory(beanFactory);
			/**
			 * 创建一个容器后,通过容器去加载BeanDefinition
			 */
			loadBeanDefinitions(beanFactory);
			this.beanFactory = beanFactory;
		}
	}

进入loadBeanDefinitions(beanFactory) 源码

@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		//为BeanFactory创建一个XmlBeanDefinitionReader实例,
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
		//设置BeanDifinitionReader环境遍历、加载资源器、资源解析器。
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
		/**
		 *初始化Reader,校验模式,自动校验or不校验。为后续的解析docment做初始化,配置文件xml是按照DTD还是XSD方式解析。默认spring按照XSD配置文件,
  		 * XSD与DTO区别:DTD :xml文件会有一行以<!DOCTYPE 开头的标示 eg:<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
  		 * 	XSD的xml文件是没有那个标示。
		 */
		
		initBeanDefinitionReader(beanDefinitionReader);
		/**
		 * 通过BeanDefinitionReader去加载bean定义。开始读取配置文件循环的获取配置文件里面的信息
		 */
		loadBeanDefinitions(beanDefinitionReader);
	}
	/**
	 * 通过XmlBeanDefinitionReader加载BeanDefinition
	 * 
	 */
	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			reader.loadBeanDefinitions(configLocations);
		}
	}
	
	@Override
	public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
		Assert.notNull(locations, "Location array must not be null");
		int counter = 0;
		for (String location : locations) {
			counter += loadBeanDefinitions(location);
		}
		return counter;
	}

上述方法中:getConfigResource()是从ClassPathXmlApplicationContext构造器传入的指定配置文件位置,进而将配置文件读取到Resource中。
getConfigLocations() 是从AbstractRefreshableConfigApplicationContext构造器传入指定配置文件,如果没有设置文件路径,默认取 /WEB-INF/applicationContext.xml
之后将配置源循环调用

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
		ResourceLoader resourceLoader = getResourceLoader();
		//删除一段不重要代码
		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				int loadCount = loadBeanDefinitions(resources);
				if (actualResources != null) {
					for (Resource resource : resources) {
						actualResources.add(resource);
					}
				}
				return loadCount;
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
		else {
			// Can only load single resources by absolute URL.
			Resource resource = resourceLoader.getResource(location);
			int loadCount = loadBeanDefinitions(resource);
			if (actualResources != null) {
				actualResources.add(resource);
			}
			return loadCount;
		}
	}

真正的开始读取文件文件源码

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Set<EncodedResource> 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();
			}
		}
	}

注意try/catch里面的这个方法doLoadBeanDefinitions();Spring源码方法已do开始就代表真正去做某些事情,在这里就是真正的加载BeanDefinitions。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			Document doc = doLoadDocument(inputSource, resource);
			return registerBeanDefinitions(doc, resource);
		}
		//删除了解析失败捕捉异常的catch
	}

上述方法通过文件流以及xml类型去获取document(xml中的文本内容),registerBeanDefinitions() 方法就开始注册document中定义的属性内容

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();
		//注册文档中属性
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		//记录本次注册bean的个数
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}
	@Override
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		logger.debug("Loading bean definitions");
		Element root = doc.getDocumentElement();
		doRegisterBeanDefinitions(root);
	}

终于到了解析标签的阶段了。doRegisterBeanDefinitions()

protected void doRegisterBeanDefinitions(Element root) {
		//下面两段代码是创建BeanDefinition解析代表,并且解析初始化<beans>标签
		//例如 获取beans标签中default-lazy-init、default-init-method等等属性,如果没有设置,spring会将这些属性设置默认值。具体默认属性请看BeanDefinitionParserDelegate类中的常量
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);
		//判断xml文件是否是默认的spring文件类型。即beans 为http://www.springframework.org/schema/beans
		if (this.delegate.isDefaultNamespace(root)) {
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					return;
				}
			}
		}
		//前置处理方法,空方法,为子类提供前置方法。
		preProcessXml(root);
		//解析<beans>标签里面的标签
		parseBeanDefinitions(root, this.delegate);
		//后置处理方法,空方法,为子类提供后置方法。
		postProcessXml(root);

		this.delegate = parent;
	}

接下来是开始解析beans标签中的属性方法源码

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		/**
		 * 检查xml文件root节点,是否是默认解析方式。
		 * 默认 http://www.springframework.org/schema/beans。
		 */
		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)) {
						/**
						 * spring默认的几个标签。import/alias/bean/beans
						 * 只有上述的几个标签是走默认的解析方式。
						 */
						parseDefaultElement(ele, delegate);
					}
					else {
						/**
						 * 定制标签解析方式。
						 */
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}

解析spring默认标签元素 parseDefaultElement()

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

从上述源码中可以看出spring支持四个默认标签import/alias/bean/beans。
接下来分别看下上述解析4个标签的源代码 ,先看下解析import都做了什么事情?

/**
	 * Parse an "import" element and load the bean definitions
	 * from the given resource into the bean factory.
	 */
	protected void importBeanDefinitionResource(Element ele) {
		//<import resource="classpath*:spring/spring-core.xml"/>
		//获取import标签上的resource属性,如果为空,跳过。
		String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
		if (!StringUtils.hasText(location)) {
			getReaderContext().error("Resource location must not be empty", ele);
			return;
		}
		// Resolve system properties: e.g. "${user.dir}"
		//解析resource属性上的表达式。
		location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

		Set<Resource> actualResources = new LinkedHashSet<>(4);

		// Discover whether the location is an absolute or relative URI
		//默认加载路径方式为相对路径。
		boolean absoluteLocation = false;
		try {
			//判断得出import标签引用的路径是绝对路径还是相对路径
			absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
		}

		// Absolute or relative?
		if (absoluteLocation) {
		// 绝对路径加载配置文件的方式。
			try {
				int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
			}
			
		}
		else {
			// No URL -> considering resource location as relative to the current file.
			//相对路径加载方式。
			try {
				int importCount;
				Resource relativeResource = getReaderContext().getResource().createRelative(location);
				if (relativeResource.exists()) {
					importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
					actualResources.add(relativeResource);
				}
				else {
					String baseLocation = getReaderContext().getResource().getURL().toString();
					importCount = getReaderContext().getReader().loadBeanDefinitions(
							StringUtils.applyRelativePath(baseLocation, location), actualResources);
				}
			}
			
		}
		Resource[] actResArray = actualResources.toArray(new Resource[0]);
		getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
	}

先会判断是否为标签,importBeanDefinitionResource()方法主要负责将引入的配置文件再次通过XmlBeanDefinitionReader#loadBeanDefinitions()方法将这些配置文件再次加载进来,源码中会根据import标签中的路径是绝对路径还是相对路径分别走不同的加载文件方式。最后都会重新用相应reader#loadBeanDefinitions()方法去加载配置文件,达到加载import配置文件里面的Bean类定义以及相关信息。