>>>>>>>>>>>>>>>>>>>>>>>>";public String getUsername() {return username;}publi。Spring5源码分析(2)-- Spring容器。" />

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

Spring容器的基本实现 1. 容器的基本用法 首先看目录结构
Spring5源码分析(2)-- Spring容器
文章图片

先来个示例:

public class UserBean { private String username = "lilei->>>>>>>>>>>>>>>>>>>>>>>>>"; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; }

resource下的spring-config.xml文件

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 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 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 } element. */ @SuppressWarnings("deprecation")// for Environment.acceptsProfiles(String...) protected void doRegisterBeanDefinitions(Element root) { // Any nested elements will cause recursion in this method. In // order to propagate and preserve 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属性究竟是什么呢?
贴段代码就知道了

这么一看就很明白了,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标签 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); } //解析beans标签元素 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }

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


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

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

    推荐阅读