Spring5源码分析(2)-- Spring容器

Spring容器的基本实现

1. 容器的基本用法

首先看目录结构
在这里插入图片描述
先来个示例:

public class UserBean {

	private String username = "lilei->>>>>>>>>>>>>>>>>>>>>>>>>";

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

resource下的spring-config.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="userBean" class="com.candy.bean.UserBean"></bean>
</beans>

Test类:


/**
 * @ClassName Test
 * @Author qzx
 * @Description TODO
 * @Date 2019/12/19
 * @Version 1.0
 */
public class Test {

	public static void main(String[] args) {
		//由于XmlBeanFactory已经过时,这里用ClassPathXmlApplicationContext代替
		//BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
		BeanFactory bf = new ClassPathXmlApplicationContext("spring-config.xml");
		UserBean userBean = (UserBean) bf.getBean("userBean");
		String username = userBean.getUsername();
		System.out.println(username);
	}
}

我们执行Test类下面的main方法,执行结果如下:
在这里插入图片描述
控制台打印出了我们获取的username,那么这段代码具体是如何运行的呢?我们一步步来分析。

2. ClassPathXmlApplicationContext源码解析

我们debug程序并进入ClassPathXmlApplicationContext方法,可以看到如下代码:

public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			//核心代码
			refresh();
		}
	}

核心代码是refresh()方法,直接点击进入会进入到AbstractApplicationContext#refresh()方法中


	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			//创建DefaultListableBeanFactory并加载配置文件 转化为BeanDefinition
			//核心方法
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				// spring扩展的实现(容器级别) BeanFactoryPostProcessor 在实例化任何用户定义的bean之前 会首先调用BFPP的接口方法
				// 常见的BFPP:PropertyPlaceholderConfigurer
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				// spring可扩展的另一个实现:BeanPostProcessor 在调用beanClass实例化前后或者调用initMethod方法的前后会调用接口方法
				// 较常见的硬编码的BPP:ApplicationContextAwareProcessor,ApplicationListenerDetecto
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				//核心方法
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

我们继续往下走,prepareRefresh();方法是刷新上下文并设置一些变量做准备工作,这里不深究,下面的一行代码ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();是加载配置文件的核心方法,我们深入研究,进入obtainFreshBeanFactory()方法内,我们最终可以到达AbstractRefreshableApplicationContext#refreshBeanFactory 方法:

/**
	 * This implementation performs an actual refresh of this context's underlying
	 * bean factory, shutting down the previous bean factory (if any) and
	 * initializing a fresh bean factory for the next phase of the context's lifecycle.
	 */
	@Override
	protected final void refreshBeanFactory() throws BeansException {
		//判断是否已经加载过beanFactory,如果已经加载过,则重新加载
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			//实例化DefaultListableBeanFactory
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
			//开始解析配置文件
			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,在实例化过程中,有一段代码需要了解

	/**
	 * Create a new AbstractAutowireCapableBeanFactory.
	 */
	public AbstractAutowireCapableBeanFactory() {
		super();
		//ignoreDependencyInterface 忽略了给定接口的自动装配功能
		ignoreDependencyInterface(BeanNameAware.class);
		ignoreDependencyInterface(BeanFactoryAware.class);
		ignoreDependencyInterface(BeanClassLoaderAware.class);
	}

这段代码的作用是忽略指定接口的自动装配功能,目前这段代码为什么需要忽略我还没弄清楚,查阅网上相关资料,初步理解为spring注册bean时正常实例化的bean和定制化的bean要有所区分,而BeanNameAware等接口的作用就是获取bean的name,beanFactory的信息等来实现定制化的bean,所以spring正常实例化的bean就要忽略这些接口。如果理解不正确请指正。
又一资料《Spring源码深度解析》说:如果A中有属性B,那么Spirng在获取A的Bean的时候如果B的属性还没有被初始化,那么Spring会自动初始化B,但是如果B实现了BeanNameAware接口的话则不会被初始化。自动装配时忽略给定依赖的接口,典型的应用是通过其他方式解析Application上下文注册,类似于BeanFactory通过BeanFactoryAware进行注入或者ApplicationContext通过ApplicationContextAware进行注入

我们继续回到refreshBeanFactory() ,customizeBeanFactory(beanFactory);方法是设置beanFactory是否允许覆盖和循环引用的属性,这里还没弄清楚具体作用,先搁置。
直接进入到loadBeanDefinitions(beanFactory);解析配置文件的方法:

@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}

这段代码中前面都是一些环境资源的配置,可以带过,重要的是loadBeanDefinitions(beanDefinitionReader);这个方法,执行代码最终会进入到AbstractBeanDefinitionReader#loadBeanDefinitions()方法中

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
		}

		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				//将String类型的地址封装到Resource数组中
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				int count = loadBeanDefinitions(resources);
				if (actualResources != null) {
					Collections.addAll(actualResources, resources);
				}
				if (logger.isTraceEnabled()) {
					logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
				}
				return count;
			}
			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 count = loadBeanDefinitions(resource);
			if (actualResources != null) {
				actualResources.add(resource);
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
			}
			return count;
		}
	}

由于我们从xml文档中获取出来的文件路径是String类型,这里将String类型的地址转换成Resource后放到Resource数组中以便后续使用,进入loadBeanDefinitions(resources)方法,看看执行了什么操作。
一路走到XmlBeanDefinitionReader#loadBeanDefinitions后可以看到如下代码

/**
	 * Load bean definitions from the specified XML file.
	 * @param resource the resource descriptor for the XML file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 */
	@Override
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(new EncodedResource(resource));
	}

进入XmlBeanDefinitionReader#loadBeanDefinitions首先对Resource使用EncodedResource进行封装,这里有一个操作 new EncodedResource(resource)这是对资源文件进行编码处理。继续进入方法内

/**
	 * Load bean definitions from the specified XML file.
	 * @param encodedResource the resource descriptor for the XML file,
	 * allowing to specify an encoding to use for parsing the file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 */
	public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isTraceEnabled()) {
			logger.trace("Loading XML bean definitions from " + encodedResource);
		}
		//通过属性来记录已加载的资源
		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 {
			//从encodedResource中获取已经封装的Resource并在此从Resource中获取其中的inputStream
			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();
			}
		}
	}

这段代码主要目的是从Resource中获取InputStream并构造InputSource,构造成功够调用doLoadBeanDefinitions方法开始真正的逻辑核心部分

/**
	* @Author: qzx
	* @Date: Created in 14:19 2019/12/23
	* @Description: TODO 通过DocumentLoader对resource文件进行转换,将resource文件转换为document文件
	* @Param: [inputSource, resource]
	* @Return: int
	*/
	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

		try {
			//加载xml文件并得到对应的Document
			Document doc = doLoadDocument(inputSource, resource);
			//根据返回的Doucment注册bean信息
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
		...
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Unexpected exception parsing XML document from " + resource, ex);
		}
	}

方法doLoadDocument(inputSource, resource);将会调用本方法中的doLoadDocument方法,

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
		return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
				getValidationModeForResource(resource), isNamespaceAware());
	}

而在这个方法中的getEntityResolver()方法目的是返回EntityResolver参数,看一下方法内部

/**
	 * Return the EntityResolver to use, building a default resolver
	 * if none specified.
	 */
	protected EntityResolver getEntityResolver() {
		if (this.entityResolver == null) {
			// Determine default EntityResolver to use.
			ResourceLoader resourceLoader = getResourceLoader();
			if (resourceLoader != null) {
				this.entityResolver = new ResourceEntityResolver(resourceLoader);
			}
			else {
				this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
			}
		}
		return this.entityResolver;
	}

介绍一下这个EntityResolver参数的作用:
在进行XML文件验证的时候,有两种验证方法–DTD&XSD,通常的我们在创建XML后是通过XML文件中的URL地址来从网络上下载相应的声明后进行验证,这样会导致一些网络的延迟,影响用户体验。
EntityResolver的作用就是提供一个如何寻找本地DTD&XSD声明文件的方法。我们将DTD&XSD声明文件下载到项目本地,直接从本地读取声明文件,就可以避免网络延迟,增加用户体验。
详细用法以后会有时间写一篇博客介绍吧
this.documentLoader.loadDocument调用的是DocumentLoader接口中的方法,而进入DocumentLoader接口的实现是在DefaultDocumentLoader

/**
	* @Author: qzx
	* @Date: Created in 17:14 2019/12/23
	* @Description: TODO
	* @Param: [inputSource, entityResolver : 项目本身可以提供一个如何寻找DTD声明的方法,即由程序来寻找DTD声明的过程,避免了通过网络来寻找相应的声明
	 * , errorHandler, validationMode, namespaceAware]
	* @Return: org.w3c.dom.Document
	*/
	@Override
	public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
		if (logger.isTraceEnabled()) {
			logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		return builder.parse(inputSource);
	}

Spring通过SAX解析XML大致都是一样的,先创建DocumentBuilderFactory,通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document,这里有时间再深究吧。
在获取到Document信息后,我们进入到下一阶段–注册bean信息,也就是进入XmlBeanDefinitionReader#doLoadBeanDefinitions.registerBeanDefinitions(doc, resource)方法内部

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		//使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		//在实例化BeanDefinitionReader时会将BeanDefinitionRegistry传入,默认使用继承自DefaultListableBeanFactory的子类
		//记录统计前BeanDefinition的个数
		int countBefore = getRegistry().getBeanDefinitionCount();
		/** 加载及注册bean */
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		//记录本次加载的BeanDefinition的个数
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

每一步的作用都做了注释,documentReader.registerBeanDefinitions(doc, createReaderContext(resource));是核心逻辑代码,进入查看

@Override
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		//对xml进行解析,核心代码
		doRegisterBeanDefinitions(doc.getDocumentElement());
	}

我们到这一步后,算是已经完成了一大半了,之前所有的工作都是解析xml的准备工作,而这一步才是真正开始解析xml文件。进入方法内部查看。

/**
	 * Register each bean definition within the given root {@code <beans/>} element.
	 */
	@SuppressWarnings("deprecation")  // for Environment.acceptsProfiles(String...)
	protected void doRegisterBeanDefinitions(Element root) {
		// Any nested <beans> elements will cause recursion in this method. In
		// order to propagate and preserve <beans> default-* attributes correctly,
		// keep track of the current (parent) delegate, which may be null. Create
		// the new (child) delegate with a reference to the parent for fallback purposes,
		// then ultimately reset this.delegate back to its original (parent) reference.
		// this behavior emulates a stack of delegates without actually necessitating one.
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);

		if (this.delegate.isDefaultNamespace(root)) {
			//处理profile属性
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				// We cannot use Profiles.of(...) since profile expressions are not supported
				// in XML config. See SPR-12458 for details.
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					if (logger.isDebugEnabled()) {
						logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
								"] not matching: " + getReaderContext().getResource());
					}
					return;
				}
			}
		}
		//解析前处理,留给子类实现
		// 代码是空的,它是面向继承而设计的,属于设计模式的模板方法模式,
		// 如果继承自DefaultBeanDefinitionDocumentReader的子类需要在bean的解析前后做一些事情的话,只需要重写这个方法就行了
		preProcessXml(root);
		//解析并注册beanDefinitions
		parseBeanDefinitions(root, this.delegate);
		//解析后处理,留给子类实现
		postProcessXml(root);

		this.delegate = parent;
	}

首先我们获取root后需要先处理profile属性,那么这个profile属性究竟是什么呢?
贴段代码就知道了

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	<beans profile="dev">
		<bean id="userBean" class="com.candy.bean.UserBean"></bean>
	</beans>
	<beans profile="pro">
	</beans>
</beans>

这么一看就很明白了,profile就是区分项目中的配置文件是开发环境还是生产环境。
接下来一大段就是处理profile属性的代码,可以不用看,继续向下,直接看parseBeanDefinitions(注释里介绍了上下两个方法)


	/**
	 * Parse the elements at the root level in the document:
	 * "import", "alias", "bean".
	 * @param root the DOM root element of the document
	 * @TODO 解析并注册BeanDefinitions
	 */
	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		//对bean的处理,根据命名空间的不同调用不同的方法,默认命名空间"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)) {
						//对bean的处理,parseDefaultElement 解析import alias bean beans标签元素
						parseDefaultElement(ele, delegate);
					}
					else {
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			//解析aop context tx 或者是用户自定义命名空间的元素
			delegate.parseCustomElement(root);
		}
	}
	/**
	* @Author: qzx
	* @Date: Created in 14:57 2019/12/24
	* @Description: TODO
	* @Param: [ele:即代表一个bean标签元素, delegate]
	* @Return: void
	*/
	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		//解析import标签<import resource="url"/>
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		//解析alias标签元素<alias name="" alias="" />
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		//解析bean标签元素<bean>
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
		//解析beans标签元素
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}

我直接粘了两个方法,首先看第一个方法parseBeanDefinitions,由于Spring在XML配置类中有两大Bean的声明,分别是
默认:

	<bean id="userBean" class="com.candy.bean.UserBean"></bean>


自定义:

<**:annotation-driven/>//支持注解

如果是默认配置,代码会执行parseDefaultElement(ele, delegate);方法对import alias bean beans标签进行解析,就是我上面粘贴的第二个方法,逻辑很简单,一看就懂。
如果是自定义配置,则会执行else中的方法。
那么默认标签和自定义标签具体是如何解析的呢?
转到默认标签解析-----
转到自定义标签解析-----
累了,有时间再写吧。。。。
未完待续…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值