本文基于《Spring源码深度解析》学习, 《Spring源码深度解析》讲解的Spring版本低于Spring3.1,当前阅读的版本为Spring5.x,因此在文章内容上会有所不一样。
这篇文章基于有必定Spring 基础的人进行讲解,因此有些问题并不作详细的实现, 若有分析不合理或是错误的地方请指教指正,不胜感激。java
在《Spring源码深度解析》中有这样一个实例:spring
public class BeanFactoryTest { @test public void testSimpleLoad() { BeanFactory bf = new XmBeanFactory(new ClassPathResource("beanFactoryTest.xml"); MyTestBean bean = (MyTestBean) bf.getBean("myTestBean"); assertEquals("testStr", bean.getTestStr()); } }
固然在这里会有一个Spring的配置文件 beanFactoryTest.xml, 当使用xml文件的时候,会发现文件头有一些<?xml ……> <beans></beans>
这样的标签, 建议学习一下DOM,DOM2, DOM3结构, 以便更加清晰的了解xml文件头中的内容的真正意义。
这里的配置文件只写一个相关的bean设计模式
<bean id="myTestBean" class="bean.MyTestBean"></bean>
这段代码的做用就是如下几点:网络
@deprecated as of Spring 3.1 in favor of {@link DefaultListableBeanFactory} and {@link XmlBeanDefinitionReader}
这是该类在当前版本的部分注释,在Spring3.1之后这个类被弃用了,Spring官方不建议使用这个类。建议使用以上这两个类。XmlBeanDefinitionReader本就是之前定义在这个类中的一个final的实例,而DefaultListableBeanFactory则是该类的超类。加载配置文件能够这样使用:app
Resource resource = new ClassPathResource("beanFactoryTest.xml"); BeanFactory beanFactory = new DefaultListableBeanFactory(); BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) beanFactory); beanDefinitionReader.loadBeanDefinitions(resource); MyTestBean bean = (MyTestBean) bf.getBean("myTestBean"); assertEquals("testStr", bean.getTestStr());
这个过程和上面的过程实际上的实现只用一点不一样、前者是在建立时就直接实例化了bean, 后者则是在加载的时候才实例化bean:ide
事实上在实际的使用中,绝大多数时候都会经过如下这种ApplicationContext的方式来加载Spring的配置文件并进行解析, 之后会写到这里的实现:函数
ApplicationContext sc = new ClassPathXmlApplicationContext("applicationContext.xml");
Resource resource = new ClassPathResource("beanFactoryTest.xml");
经过ClassPathResource 加载配置文件,并构建该实例的时候,是使用Resource接口进行定义的, 这也就说明了建立的其实是Resource的实例,经过查看Resource 的源码不难发现,Resource对Java中将要使用的资源进行了抽象,Spring的设计中几乎全部能够加载资源的
类须要直接或间接的实现Resource 这个接口。下面能够看一下这个接口:学习
boolean exists(); // 判断是否资源是否存在 default boolean isReadable() { // 判断资源是否可读 return exists(); } default boolean isOpen() { // 判断文件是否打开 return false; } default boolean isFile() { // 判断文件是不是文件系统中的文件,Spring5.0后加入的 return false; } URL getURL() throws IOException; // 获取文件的URL URI getURI() throws IOException; // 获取文件的URI File getFile() throws IOException; // 获取文件 default ReadableByteChannel readableChannel() throws IOException { // 返回一个Channel, 拥有最大效率的读操做 return Channels.newChannel(getInputStream()); } long contentLength() throws IOException; // 返回资源解析后的长度 long lastModified() throws IOException; // 最后一次休干时间 Resource createRelative(String relativePath) throws IOException; // 基于当前资源建立一个相对资源 @Nullable String getFilename(); // 获取文件名 for example, "myfile.txt" String getDescription(); // 获取资源描述, 当发生错误时将被打印
经过查看源码,还有一点能够发现, Resource接口继承了InputStreamSource 接口,下面来看下这个接口:ui
public interface InputStreamSource { /** * Return an {@link InputStream} for the content of an underlying resource. * <p>It is expected that each call creates a <i>fresh</i> stream. * <p>This requirement is particularly important when you consider an API such * as JavaMail, which needs to be able to read the stream multiple times when * creating mail attachments. For such a use case, it is <i>required</i> * that each {@code getInputStream()} call returns a fresh stream. * @return the input stream for the underlying resource (must not be {@code null}) * @throws java.io.FileNotFoundException if the underlying resource doesn't exist * @throws IOException if the content stream could not be opened */ InputStream getInputStream() throws IOException; }
这个接口的做用很是简单而且是顶层接口,它的做用就是返回一个InputStream, 最简单的做用却提供了最大的方便, 由于全部资源加载类几乎都直接或间接的实现了Resource, 这也就意味着, 几乎全部的资源加载类均可以获得一个InputStream, 这将使得在资源加载以后能轻易地获得一个InputStream, 这很是重要。经过InputStream, Spring使全部的资源文件都能进行统一的管理了, 优势是不言而喻的。至于实现是很是简单的, ClassPathResource 中的实现方式即是经过class或者classLoader提供的底层方法进行调用的, 对于FileSystemResource的实现其实更加简单, 就是直接使用FileInputStream对文件进行实例化。this
配置文件加载完成后进行的下一步操做是这样的,这和3.1以前的版本不太同样,至于为何要弃用XmlBeanFactory(我猜是为了对其余部分进行设计,从而让这部分代码更加充分的进行解耦):
BeanFactory beanFactory = new DefaultListableBeanFactory(); BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) beanFactory); beanDefinitionReader.loadBeanDefinitions(resource);
配置文件加载完成后,建立了一个BeanFactory的实例。DefaultListableBeanFactory: 见名知意,这是一个默承认列的bean工厂类,注释用说道,典型的一个应用就是在第一次定义时注册全部bean。接下来的操做是利用多态将上一步建立的BeanFactory的实例转成BeanDefinitionRegistry, 由于下一步须要读取xml文件中定义的内容,这也就是XmlBeanDefinitionReader的做用,而XmlBeanDefinitionReader在实例化的时候须要一个bean的定义注册机,因此就进行了以上操做, 事实上:在建立BeanFactory实例时,一样能够定义为BeanDefinitionRegistry类型。下面详细说下一这三个类的做用:
/** * Bean definition reader for XML bean definitions. * Delegates the actual XML document reading to an implementation * of the {@link BeanDefinitionDocumentReader} interface. * * <p>Typically applied to a * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory} * or a {@link org.springframework.context.support.GenericApplicationContext}. * * <p>This class loads a DOM document and applies the BeanDefinitionDocumentReader to it. * The document reader will register each bean definition with the given bean factory, * talking to the latter's implementation of the * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry} interface. */ 只说最重要的一个部分, 在这里它须要委托一个真正的xml文档读取器来读取文档内容,也就是BeanDefinitionDocumentReader,而这个文档读取器将读取全部的bean注册内容,而这些资源正是一、2中所获得的。
接下来就是重要的一步,beanDefinitionReader.loadBeanDefinitions(resource); 在解析了配置文件中的bean后,事实上配置文件中bean并无被真正的加载,而且上面的步骤也只是对全部的bean进行了一次注册, 因此,这个时候load了resoure中的内容, 在编码没有问题之后,而且resource中bean能够在类加载器下找到这些类,这时就对这些bean进行加载,实例化。下面跟踪代码到这个实现中看看Spring 是怎么作的:
/** * Create new XmlBeanDefinitionReader for the given bean factory. * @param registry the BeanFactory to load bean definitions into, * in the form of a BeanDefinitionRegistry */ public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) { super(registry); } protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); this.registry = registry; // Determine ResourceLoader to use. if (this.registry instanceof ResourceLoader) { this.resourceLoader = (ResourceLoader) this.registry; } else { this.resourceLoader = new PathMatchingResourcePatternResolver(); } // Inherit Environment if possible if (this.registry instanceof EnvironmentCapable) { this.environment = ((EnvironmentCapable) this.registry).getEnvironment(); } else { this.environment = new StandardEnvironment(); } }
在实例化XmlBeanDefinitionReader 的过程当中,在构造函数中调用了其超类的构造函数,而在超类中对其所处换环境进行的判断,所谓的环境呢,事实上指得就是是经过BeanFactory, 仍是经过ApplicationContext加载的上下文,这也就意味着不一样方式加载可能存在某些不一样。写这些的目的实际上是为了引出这里的一个咱们十分关注的东西, 就是自动装配。在AbstractAutowireCapableBeanFactory这个抽象类的构造方法中实现了相关的自动装配,在BeanDefinitionRegistry 和DefaultListableBeanFactory中都继承了这个抽象类, 并在其构造函数内直接调用了其超类的构造函数也就是:
/** * Create a new AbstractAutowireCapableBeanFactory. */ public AbstractAutowireCapableBeanFactory() { super(); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
这里有必要说起一下
ignoreDependencyInterface();
这个方法。它的主要功能就是忽略接口中的自动装配, 那么这样作的目的是什么呢?会产生什么样的效果呢?举例来讲, 当A中有属性B, 那么Spring在获取A的时候就会去先去获取B, 然而有些时候Spring不会这样作,就是Spring经过BeanNameAware、BeanFactoryAware和BeanClassLoaderAware进行注入的, 也就是根据环境的不一样, Spring会选择相应的自从装配的方式。在不是当前环境中的注入,Spring并不会再当前环境对Bean进行自动装配。相似于,BeanFactory经过BeanFactoryAwar进行注入或者ApplicationContext经过ApplicationContextAware进行注入。
通过了这么长时间的铺垫,终于应该进入正题了, 就是进入经过loadBeanDefinitions(resource)方法加载这个文件。这个方法这样实现:
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 { 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(); } } }
这段代码很容易理解,不过真正实现的核心代码是在return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
这里实现的。下面是这个方法的核心代码:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } }
这段处理能够说很是容易了,对resource流进行再封装,封装为Docment对象,而后解析并注册这个doc中的bean对象,返回定义的bean的个数。在Spring3.1以前上面这个方法中还要验证加载Xml是否符合规范。而Spring5.x以后Spring将验证的工做放到了获取Document中。
看一下Document doc = doLoadDocument(inputSource, resource);这个方法的源码:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
在这个方法中作了三件事:
实现上面方法的类继承了这个接口:DocumentLoader,而且实现了这个接口中的惟一的抽象:
/** * Load a {@link Document document} from the supplied {@link InputSource source}. * @param inputSource the source of the document that is to be loaded * @param entityResolver the resolver that is to be used to resolve any entities * @param errorHandler used to report any errors during document loading * @param validationMode the type of validation * {@link org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_DTD DTD} * or {@link org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_XSD XSD}) * @param namespaceAware {@code true} if support for XML namespaces is to be provided * @return the loaded {@link Document document} * @throws Exception if an error occurs */
Document loadDocument(
InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception;
那么详细讲一下上面说起的EntityResolver, 若是SAX(Simple API for XML:简单的来说,它的做用就是不去构建DOM,而是以应用程序的方式以最有效率的方式实现XML与应用实体之间的映射;固然还有一种方式是解析DOM,具体两种方式,我也没有作过相应深刻探究)应用驱动程序须要实现自定义的处理外部实体,在必须实现此接口并经过某种方式向SAX驱动器注册一个实例。这须要根据XML头部的DTD中的网络地址下载声明并认证,而EntityResolver实际上就是在提供一个寻dtd找声明的方法,这样就能够在项目中直接定义好声明,而经过本地寻找的方式避免了网络寻找的过程,编译器也避免了在网络延迟高或没有网络的状况下报错。
当文件转换为Document后,接下来提取及注册Bean就是咱们后头的重头戏。事实上,这一部份内容并不会在咱们的使用中出现了。
一样在XmlBeanDefinitionReader这个类中,能够发现随着Docment的获取完成后,直接作的是下面的这个事情registerBeanDefinitions();:
/** * Register the bean definitions contained in the given DOM document. * Called by {@code loadBeanDefinitions}. * <p>Creates a new instance of the parser class and invokes * {@code registerBeanDefinitions} on it. * @param doc the DOM document * @param resource the resource descriptor (for context information) * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of parsing errors * @see #loadBeanDefinitions * @see #setDocumentReaderClass * @see BeanDefinitionDocumentReader#registerBeanDefinitions */ 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; }
做用在注释中写的很清楚,注册DOM文档(Spring的配置信息中. 也就是解析后的xml)中包含的bean。
DefaultBeanDefinitionDocumentReader实现了BeanDefinitionDocumentReader这个接口,这里惟一的抽象方法就是registerBeanDefinitions(Document doc, XmlReaderContext readerContext);,这也就意味着上面的第三行代码是一个天然的应用。
提示:这里的doc参数就是经过以前的doLoadDocument方法得到的,而这很好的应用了面向对象的单一职责原则, 将转换为Docment的复杂过程交给一个单一的类处理,而这个类就是BeanDefinitionDocumentReader, 事实上这是一个接口,而具体的实例化是在createBeanDefinitionDocumentReader这个方法中完成的。
到这里我已经不想探究xml文件是如何读取的了,若是想看的话,能够去看下一篇《Spring源码一(容器的基本实现2)》!