Spring源码5.0:容器的基本实现

bean是Spring中最核心的东西

bean本无特殊之处。Spring的目的就是让bean成为一个纯粹的POJO。
spring framework中使用xml配置bean-------bean的声明方式

配置beans.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="student" class="test.Student"/>
</beans>

测试

public static void main(String[] args) {
		XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("beans.xml"));
		Student bean = (Student) bf.getBean("student");
		System.out.println(bean); 
    }

虽然日常是使用ApplicationContext
这里使用Bean Factory

得知整个bean的实例化大概需要3个大逻辑
读取和验证配置文件
根据配置文件中的配置进行反射实例化
完成整个逻辑串联

源码

实现上面功能的是org.Springframework.bean.jar
Core包也是必需的

beans包的层级结构

src/main/java 用于展现Spring的主要逻辑
src/main/resources 用于存放系统的配置文件
src/test/java 主要逻辑进行单元测试
src/test/resources 存放测试用的配置文件

核心类

  • DefaultListableBeanFactory

ioc容器之始祖

XmlBeanFactory 继承自DefaultListableBeanFactory
DefaultListableBeanFactory为整个bean加载的核心部分。是Spring注册及加载bean的默认实现
XmlBeanFactory 和DefaultListableBeanFactory 不同之处在于 XmlBeanFactory 使用了自定义XML读取器 XmlBeanDefinitionReader,实现了个性化BeanDefinitionReader读取。
DefaultListableBeanFactory继承了AbstractBeanDefinitionReader并实现了 ConfigurableListableBeanFactory
avatar

  • XmlBeanDefinitionReader

XML配置读取是Spring中重要的功能。因为大部分功能都是以配置作为切入点
avatar

容器的基础XmlBeanFactory

XmlBeanFactory初始化时序图

avatar

按照时序图看运行的类

配置文件封装 Spring的配置文件读取是通过ClassPathResource进行封装的
Java中,将不停来源的资源抽象成URL。通过注册不同的handler(URLStreamHandler)出来不同来源的资源的读取逻辑。
一般handler的类型使用不同前缀(协议,Protocol)识别,如“file","http:"等。
但是URL没有默认定义相对的Classpath或ServletContext等资源的handler

可注册自己的URLStreamHandler解析特点URL前缀(协议),如"classpath:",但是需要了解URL的实现机制,但URL没提供基本的方法。如检查资源是否存在。检查当前资源是否可读。

Spring所以使用到了自己的抽象结构:Resource接口封装底层资源。

org.springframework.core.io

public interface InputStreamSource { 
InputStream getInputStream() throws IOException; 
} 
public interface Resource extends InputStreamSource { 
boolean exists(); 
boolean isReadable(); 
boolean isOpen(); 
URL getURL() throws IOException; 
URI getURI() throws IOException; 
File getFile() throws IOException; 
long contentLength() throws IOException; 
long lastModified() throws IOException; 
Resource createRelative(String relativePath) throws IOException; 
String getFilename(); 
String getDescription(); 
} 


InputStreamSource 封装任何能返回InputStream的类,如FIle,Classpath下的资源和ByteArray等。
只要一个方法定义:getInputStream()。其返回一个新的InputStream对象。

Resouce接口抽象了所有Spring内部使用到的底层资源:File,URL,Classpath等。3个判定资源状态的方法。
还提供了不同资源到URL,URI,FIle类型的转换。以及获取lastModified 和getFIlename
还提供了基于当前足以创建一个相对资源的方法,createRelative()。
在错误处理中需要详细地打印出错的资源文件,getDescription(),用于在错误处理中打印信息。

对于不同来源的资源文件都有相应的Resource实现

  • FileSystemResource
  • ClasspathResource
  • URLResource
  • InputStreamResource
  • ByteArrayResource

当通过Resource相关类完成了对配置文件进行封装后配置问读取工作全权交给XmlBeanDefinitionReader处理

Spring中将配置文件封装为Resource类型的实例方法后
接下来是XmlBeanFactory的初始化过程。其初始化有很多方法,Spring提供了很多构造函数。

public XmlBeanFactory(Resource resource) throws BeansException {
   this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
   super(parentBeanFactory);
   this.reader.loadBeanDefinitions(resource);
}

this.reader.loadBeanDefinitions(resource)才是资源加载的真正实现。
对应时序图的XmlBeanDefinitionReader,其在加载数据中还有一个调用父类构造函数初始化过程super(parentBeanfactory)
跟踪到其父类代码AbstractAutoWireCapableBeanFactory的构造函数中

public AbstractAutowireCapableBeanFactory() {
   super();
   ignoreDependencyInterface(BeanNameAware.class);
   ignoreDependencyInterface(BeanFactoryAware.class);
   ignoreDependencyInterface(BeanClassLoaderAware.class);
}

ignoreDependencyInterface方法。目的是忽略给定接口的自动装配功能
既忽略DI注入

加载Bean

loadBeanDefinitions 时序图

avatar

时序图分析:
  • 封装资源文件。进入XmlBeanDefinitionReader后对参数resource使用EncodedResource类进行封装
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreExeception{
 return loadBeanDefinitions(new EncodedResource(resource)):
 }

  • 获取输入流。从Resource中获取对应InputStream并构造InputSource
  • 通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions

*EncodedResource作用:对资源文件的编码进行处理。设置好了编码属性的时候Spring会使用相应的编码作为输入流的编码。
构造了一个有编码(encoding)的INputStreamReader。当构造好encodedResource对象后,再次传入了可复用方法loadBeanDefinitions(new EncodedResource(resource))

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
   Assert.notNull(encodedResource, "EncodedResource must not be null");
   if (logger.isInfoEnabled()) {
      logger.info("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();
      }
   }
}

进入了doLoadBeanDefinitions后。因为之前对resource参数封装,其考虑到了Resource可能存在编码要求。接着,通过SAX读取XML文件的方式准备InputSource对象

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {
   try {
      Document doc = doLoadDocument(inputSource, resource);
      return registerBeanDefinitions(doc, resource);
   }
   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);
   }
}

此代码重点

  • 获取对XML文件的验证模式
  • 加载XML文件,得到想对象的Document
  • 根据返回的Document注册Bean信息

获取XML的验证模式

两种验证模式DTD 和XSD
DTD,Document Type Definition 。文档类型定义。是一种XML约束类型语言,XML文件验证机制。属于XML文件组成的一部分。DTD保证XML文档格式有效性。
DTD需要在XML文件的头部声明

XSD,XML Schemas Definition。XML Schema描述了XML文档的结构。保证XML文档的结构
一个声明在DOCTYPE
一个声明在内部

通过getValidationModeForResource(resource)获取对应的资源的验证模式,其包含在doLoadDocument函数里头
XmlBeanDefinitionReader.class

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

protected int getValidationModeForResource(Resource resource) {
   int validationModeToUse = getValidationMode();
   if (validationModeToUse != VALIDATION_AUTO) {
      return validationModeToUse;
   }
   int detectedMode = detectValidationMode(resource);
   if (detectedMode != VALIDATION_AUTO) {
      return detectedMode;
   }
   // Hmm, we didn't get a clear indication... Let's assume XSD,
   // since apparently no DTD declaration has been found up until
   // detection stopped (before finding the document's root tag).
   return VALIDATION_XSD;
}

按照注释,现在已经少用DTD了

接着根据里面的detectValidationMode(resource)读取验证

获取Document

验证模式准备后交给Document加载。委托给了DocumentLoader(接口)。主调用为DefaultDocumentLoader
DocumentLoader.class 接口

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

DefaultDocumentLoader.class 实现

@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
      ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

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

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

loadDocument其中参数:EntityResolver。SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动注册一个实例。

eg:解析一个XML,SAX首先读取该XML文档上的生命。根据声明寻找相应的DTD定义,以便对文档进行一个验证。缺省的寻找规则(通过网络,实现声明的DTD的URI地址,下载相应的DTD声明,并进行认证)。因为网络如果不可用时,会不可用,DTD声明未找到。
EntityResolver作用为提供一个如何寻找DTD声明的方法。即由程序来实现寻找DTD声明的过程。如将DTD文件放到项目的某处,实现时,直接返回此文档到SAX。避免了网络寻找相应的声明。

entityResolver接口方法声明

InputSource resolverEntity(String publicId,String systemId)

XSD 和 DTD 两个格式分别获取的两个Id不同

Spring使用了DelegatingEntityResolver类为EntityResolver的实现类
DelegatingEntityResolver.class

@Override
@Nullable
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
      throws SAXException, IOException {

   if (systemId != null) {
      if (systemId.endsWith(DTD_SUFFIX)) {
         return this.dtdResolver.resolveEntity(publicId, systemId);
      }
      else if (systemId.endsWith(XSD_SUFFIX)) {
         return this.schemaResolver.resolveEntity(publicId, systemId);
      }
   }

   // Fall back to the parser's default behavior.
   return null;
}

可得对于不同验证模式,Spring使用了不同解析器解析
加载DTD类型的BeansDtdResolver的resolveEntity是直接截取systemId最后的xx.dtd然后去当前路径下寻找
加载XSD的PluggableSchemaResolver类的resolveEntity是默认到META-INF/spring.schemas找到systemid所对应得XSD文件并加载

解析及注册BeanDefinitions

文件转换为Document后,接下来提取及注册bean。
程序此时已经拥有XML文档文件得Document实例对象

DefaultBeanDefinitionDocumentReader.class

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

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   logger.debug("Loading bean definitions");
   Element root = doc.getDocumentElement();
   doRegisterBeanDefinitions(root);
}

提取root,以root作为参数继续BeanDefinition的注册

之前在XML加载解析的准备阶段
现在开始使用doRegisterBeanDefinitions解析
DefaultBeanDefinitionDocumentReader.class

protected void doRegisterBeanDefinitions(Element root) {
   BeanDefinitionParserDelegate parent = this.delegate;
   this.delegate = createDelegate(getReaderContext(), root, parent);

   if (this.delegate.isDefaultNamespace(root)) {
//处理profile属性,标注dev,prod。环境
      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)) {
            if (logger.isInfoEnabled()) {
               logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                     "] not matching: " + getReaderContext().getResource());
            }
            return;
         }
      }
   }

//解析前处理,留给子类实现
   preProcessXml(root);
   parseBeanDefinitions(root, this.delegate);
//解析后处理,留给子类实现  
 postProcessXml(root);

   this.delegate = parent;
}

专门处理解析
DefaultBeanDefinitionDocumentReader.class

protected BeanDefinitionParserDelegate createDelegate(
      XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {

   BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
   delegate.initDefaults(root, parentDelegate);
   return delegate;
}

划重点
一个类要么是面向继承的设计的,要么就用final修饰
此用了模板方法
解析,并注册BeanDefinition
DefaultBeanDefinitionDocumentReader.class


protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
   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)) {
               parseDefaultElement(ele, delegate);
            }
            else {
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
      delegate.parseCustomElement(root);
   }
}

XML配置里面有两大Bean声明,缺省
bean id=“test” class=“test.TestBean”
自定义
tx:annotation-driven
对于根节点or子节点如果是默认命名空间的化采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElement方法对自定义命名空间进行解析。
判断是否默认or自定义是使用node.getNamespaceURI()获取命名空间,与Spring中固定命名空间http://www.springframework.org/schema/beans 进行比对

加载XML,解析XML到此结束

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值