通俗理解spring源码(二)—— 资源定位与加载
通俗理解spring源码(二)—— 资源定位与加载
最开始学习spring的时候,一般都是这样用:
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); User user = (User)context.getBean("user");
这里的ApplicationContext也是一个容器,只不过是引用了一个DefaultListableBeanFactory,暂时先不用管,真正的容器还是DefaultListableBeanFactory,我们还是以DefaultListableBeanFactory的初始化为例,可以这样写:
public static void main(String[] args) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
BeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
Resource resource = new ClassPathResource("spring.xml");
reader.loadBeanDefinitions(resource);
User user = (User)factory.getBean("user");
}
1、DefaultListableBeanFactory实例化
- new DefaultListableBeanFactory()这步操作实例化了一个工厂对象,初始化了一个容器,最终所有的bean都会放到这个容器中。
- 在它的构造器中,首先调用父类构造:
public DefaultListableBeanFactory() { super(); }
- 进入抽象类AbstractAutowireCapableBeanFactory中,AbstractAutowireCapableBeanFactory是DefaultListableBeanFactory的抽象父类,不记得的可以看看我上一篇博文,有个大致印象即可。在该抽象类构造中:
public AbstractAutowireCapableBeanFactory() { super(); //添加忽略给定接口的自动装配功能 ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
- 这里的super(),可以点进去看看,没有任何操作。
- ignoreDependencyInterface(),表示添加忽略给定接口的自动装配功能,暂不做详细介绍,大家可以参考这篇文章。
2、Resource资源封装
- spring将所有的资源都封装成一个Resource,包括文件系统资源(FileSystemResource)、classpath资源(ClassPathResource)、url资源(UrlResource)等。
- Resource接口定义了获取当前资源属性的方法,如存在性(Exists)、可读性(isReadable)、是否处于打开状态(isOpen)等。
-
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}public interface Resource extends InputStreamSource { boolean exists(); default boolean isReadable() { return exists(); } default boolean isOpen() { return false; } default boolean isFile() { return false; } URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; default ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String relativePath) throws IOException; @Nullable String getFilename(); String getDescription();
-
2、XmlBeanDefinitionReader实例化
- xml配置文件的读取是spring中最重要的功能,因为spring的大部分功能都是以配置作为切入点的。
-
XmlBeanDefinitionReader负责读取和解析已封装好xml配置文件的Resource,即ClassPathResource。
- 这里先大致了解一下spring的资源加载体系:
-
- ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource,如根据不同的前缀“file:”“http:”“jar:”等判断不同的资源。
- BeanDefinitionReader:主要定义资源文件读取并转换为BeanDefinition的各个方法。BeanDefinition就是对你所定义的bean的class、id、alias、property等属性的封装,此时还没有实例化bean。
- EnvironmentCapable:定义获取Environment方法,Environment就是对当前所激活的profile的封装。
- DocumentLoader:定义从资源文件加载到转换为Document的功能。Document是对xml文档的封装,从中可以获取各个节点的数据。
- AbstractBeanDefinitionReader:对EnvironmentCapable、BeanDefinitionReader类定义的功能进行实现。
- BeanDefinitionDocumentReader:定义读取Document并注册BeanDefinition功能。
- BeanDefinitionParserDelegate:定义解析Element的各种方法。真正解析xml的就是这个类,典型的委派模式。
3、loadBeanDefinitions方法
loadBeanDefinitions是整个资源加载的切入点,下面是该方法执行时序图:
在该方法中会调用XmlBeanDefinitionReader的重载方法loadBeanDefinitions(Resource resource)。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
EncodedResource是处理资源文件的编码的,其中主要逻辑体现在getReader()方法中,我们可以在EncodedResource构造器中设置编码,spring就会使用相应的编码作为输入流的编码。
public Reader getReader() throws IOException { if (this.charset != null) { return new InputStreamReader(this.resource.getInputStream(), this.charset); } else if (this.encoding != null) { return new InputStreamReader(this.resource.getInputStream(), this.encoding); } else { return new InputStreamReader(this.resource.getInputStream()); } }
处理完编码后,进入到loadBeanDefinitions(new EncodedResource(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 { //从encodedResource中取出原来的resource,得到输入流inputStream InputStream inputStream = encodedResource.getResource().getInputStream(); try { //inputSource不属于spring,是解析xml的一个工具,全路径为org.xml.sax.InputSource 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参数做封装,目的是考虑到resource可能存在编码要求的情况,其次,用过SAX读取xml文件的方式离开准备InputSource对象,最后将准备的数据通过参数传入真正的核心处理部分doLoadBeanDefinitions(inputSource, encodedResource.getResource())。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //从资源文件转换为document对象 Document doc = doLoadDocument(inputSource, resource); //解析document,并注册beanDefiniton到工厂中 int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
这个方法的核心代码就两句,
//从资源文件转换为document对象 Document doc = doLoadDocument(inputSource, resource); //解析document,并注册beanDefiniton到工厂中 int count = registerBeanDefinitions(doc, resource);
其中registerBeanDefinitions(doc, resource)又是核心,逻辑非常复杂,先来看doLoadDocument(inputSource, resource)方法:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
这里获取对xml文件的验证模式,加载xml文件,并得到对应的document,获取xml的验证模式将在下一篇博客中讲解。
走的太远,不要忘记为什么出发!现在再来看看这段代码:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); BeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); Resource resource = new ClassPathResource("spring.xml"); reader.loadBeanDefinitions(resource);
是不是清晰一些?总结如下:
- 实例化工厂,作为bean的容器;
- 实例化BeanDefinitionReader,负责读取xml;
- 将资源文件路径封装为对应的resource对象;
- 调用reader.loadBeanDefinitions(resource),实际上会先将resouce转换为document,再将document转换为beanDefinition,这一步是整个资源加载的核心。
参考:spring源码深度解析