Spring|Spring 源码分析之IOC容器的基本实现

说到Spring 大家可能都知道,也都用过 ,那么今天就给大家来解析一下,Spring 从解析Xml 到最后的创建Bean 都用到了 什么类,用到了那些模式>
BeanFactor beanfactor = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));

功能分析:
1.读取配置文件beanFactory.xml
2.根据beanFacotry.xml 找到配置所对应的类,并且初始化。
3.调用后实例化。
Spring 解析Xml读取Bean 核心类介绍 -DefaultListableBeanFacotry 和XmlBeanDefintionReader
1.XmlBeanFactory
XmlBeanFacotry继承自DefaultListableBeanFactory ,而DefaultListableBeanFacotry是整个加载bean的核心部分,是Spring注册及加载bean的默认实现,而XmlBeanfactory与DefaultListableBeanFactory不同的地方在于 XmlBeanFactory 实现了自定义的Xml读取器XmlBeanDefinationReader实现了BeanDefinitionReader读取,
2.XmlBeanDefinitionReader
XmlBeanDefintionReader 读中有资源文件读取,解析以及注册bean 的类 ,如:ResourceLoader,BeanDefinitionReader DocumentLoader 等等我就不列举了
容器的基础XmlBeanFactory
1.对xml文件的封装
Spring 通过对配置文件的读取封装到对应的类中,如,New ClassPathResource("beanfacotry.xml"); 那么ClassPathResouce完成了什么功能呢?
1.将当前URL封装到Resouce 我们知道URL 没有默认定义ClassPath或者SerevletContext 等资环的handler,此时Spring 自己底层封装了Resouce 即URl封装底层资源。代码如下
public interface Resource extends InputStreamSource{ boolean exists(); boolean isReadable(); boolean isOpen(); URL getURL() throws IOException; URI getURI() throws IOException; ....... }

这里我们可以借鉴一下,Spring读取xml的方式
Resource resource = new ClassPathResource("bean.xml");
InputStream inputStream = resouce.getInputStream();
Spring 是如何通过 .xml 得到ClassPathResource对象呢 ?其实很简单,我们用IDEA编辑器打开ClassPathResource 对象看看
public InputStream getInputStream() throws IOException { InputStream is; if (this.clazz != null) { is = this.clazz.getResourceAsStream(this.path); } else { is = this.classLoader.getResourceAsStream(this.path); }if (is == null) { throw new FileNotFoundException(this.getDescription() + " cannot be opened because it does not exist"); } else { return is; } }

当通过Resource 相关类对xml文件进行封装后具体对配置文件的读取工作就交个XmlBeanDefinitionReader来处理了。
我们可以用IDEA 打开XmlBeanFacotry debug下
public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader; public XmlBeanFactory(Resource resource) throws BeansException { this(resource, (BeanFactory)null); //调用XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) 的构造方法 }public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); //调用父类的构造器初始化忽略给定接口的装配功能 this.reader = new XmlBeanDefinitionReader(this); this.reader.loadBeanDefinitions(resource); //loadBeanDefinitions 才是加载bean的真正位置 } }

调用loadBeanDefinitions 加载Bean 跟踪一下
XmlBeanDefinitionReader.java
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (this.logger.isInfoEnabled()) { this.logger.info("Loading XML bean definitions from " + encodedResource.getResource()); } //通过属性来记录 已近加载的资源 Set currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); }if (!((Set)currentResources).add(encodedResource)) { throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } else { int var6; try { InputStream inputStream = encodedResource.getResource().getInputStream(); //从encodedResource中获取已近封装的Resource对象 并在此从Resource中获取InpuStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); //设置编码 } //这里才是真正的加载bean的部分 var6 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException var15) { throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15); } finally { ((Set)currentResources).remove(encodedResource); if (((Set)currentResources).isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); }} return var6; } }

调用doLoadBeanDefinitions 加载bean 再次进入到doLoadBeanDefinitions 具体的方法中
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { int validationMode = this.getValidationModeForResource(resource); //获取xml的验证模式 Document doc = this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, validationMode, this.isNamespaceAware()); //加载xml文件 获取Document 对象 return this.registerBeanDefinitions(doc, resource); //获取Document 注册bean信息 } catch (BeanDefinitionStoreException var5) { throw var5; } catch (SAXParseException var6) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var6.getLineNumber() + " in XML document from " + resource + " is invalid", var6); } catch (SAXException var7) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var7); } catch (ParserConfigurationException var8) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var8); } catch (IOException var9) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var9); } catch (Throwable var10) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var10); } }

1.获取xml验证模式
我们知道xml验证 有种验证方式XSD(Xml Schemas Definition)和DTD (Document type Definition)两种验证方式

这种为Dtd的声明方式

这种为Xsd 的声明方式
其中,如果没有指明 声明方式 就是用默认的声明方式
protected int getValidationModeForResource(Resource resource) { int validationModeToUse = this.getValidationMode(); if (validationModeToUse != 1) { //如果手动了制定了验证模式则使用指定的验证模式 return validationModeToUse; } else { //否则就是 默认的验证方式使用自动检测 int detectedMode = this.detectValidationMode(resource); return detectedMode != 1 ? detectedMode : 3; } }

protected int detectValidationMode(Resource resource) { if (resource.isOpen()) { throw new BeanDefinitionStoreException("Passed-in Resource [" + resource + "] contains an open stream: " + "cannot determine validation mode automatically. Either pass in a Resource " + "that is able to create fresh streams, or explicitly specify the validationMode " + "on your XmlBeanDefinitionReader instance."); } else { InputStream inputStream; try { inputStream = resource.getInputStream(); } catch (IOException var5) { throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " + "Did you attempt to load directly from a SAX InputSource without specifying the " + "validationMode on your XmlBeanDefinitionReader instance?", var5); }try { return this.validationModeDetector.detectValidationMode(inputStream); //这里才是真正的获取验证模式 } catch (IOException var4) { throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: an error occurred whilst reading from the InputStream.", var4); } } }

那我们可以看detectValidationMode 里面是如何处理的
public int detectValidationMode(InputStream inputStream) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); try { boolean isDtdValidated = false; while(true) { String content; if ((content = reader.readLine()) != null) { content = this.consumeCommentTokens(content); if (this.inComment || !StringUtils.hasText(content)) {//如果是注释或者行或者是空的则跳过 continue; } if (this.hasDoctype(content)) {// hasDoctype如果 content.indexOf("DOCTYPE") > -1; 则为DTD 验证 isDtdValidated = true; } else if (!this.hasOpeningTag(content)) {//否则就是XSD continue; } }int var6 = isDtdValidated ? 2 : 3; return var6; } } catch (CharConversionException var9) { ; } finally { reader.close(); }return 1; }

获取Document 经过了获取验证模式 就可以进行 Document的加载了 同样在XmlBeanFacotry类中,对于文档的读取并没有真正的而在这里解析而是委托给 了DocumentLoader去执行
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { //创建DocumentBuilderFactory 对象 其中调用newsInstance 方法//FactoryFinder.find( DocumentBuilderFactory.class, // "javax.xml.parsers.DocumentBuilderFactory" "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"); 返回DocumentBuilderFactory 的实现类 DocumentBuilderFactoryImplDocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); }DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler); //根据DocumentBuilderFactoryImpl 创建DocumentBuilder对象 return builder.parse(inputSource); //真正的创建Document 对象 }

1.加载Document 这里也简单 就是获取DocumentBuilderFactory 的实现类DocumentBuilderFactoryImpl
2.根据DocumentBuilderFactoryImpl 获取DocumentBuilder
3.通过DocumentBuilder 来获取Document
这里有必要说明一下 在调用loadDocument 之前调用的getEntityResolver()方法
调用getEntityResolver 方法 返回 EntityResolver 对象,EntityResolver的作用
1.获取SystemId 和publicId
如果 为XSD 的验证模式
SystemId : http://www.springframework.org/schema/beans/spring-beans.xsd
publicId :null
DTD的验证模式
SystemId: http://www.springframework.org/schema/beans/spring-beans.xsd
publicId: //SPRING//DTD BEAN 2.0//EN
解析及注册BeanDefinitions 当把xml文件转换成Document 后,接下来 就是提取和注册bean 了
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader(); //实例化BeanDefinitionDocumentReader documentReader.setEnvironment(this.getEnvironment()); //将环境变量设置其中 int countBefore = this.getRegistry().getBeanDefinitionCount(); //记录统计前BeanDefinitions的加载个数 documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource)); //加载和注册bean return this.getRegistry().getBeanDefinitionCount() - countBefore; //记录统计后BeanDefinitions的加载个数 }

其中document 是上一节 loadDocument加载转换出来的,在这个方法中很好的体现了 单一制则原则,将逻辑委托给单一的类处理, 而这个逻辑处理类就是BeanDefinitionReaderDocumentReader 。而 BeanDefinitionReaderDocumentReader 的类型已经是DefaultBeanDefinitionReaderDocumentReader 了,进入registerBeanDefinitions 的方法 其实目的只有一个就是提取Root
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; this.logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); this.doRegisterBeanDefinitions(root); //这里才是真正的注册bean }

山路18弯 ,如果说我们以前一直是XMl解析的准备阶段,那么现在, 我们真正的开始解析了,
protected void doRegisterBeanDefinitions(Element root) { String profileSpec = root.getAttribute("profile"); //处理profile if (StringUtils.hasText(profileSpec)) { Assert.state(this.environment != null, "environment property must not be null"); String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; "); if (!this.environment.acceptsProfiles(specifiedProfiles)) { return; } }BeanDefinitionParserDelegate parent = this.delegate; this.delegate = this.createHelper(this.readerContext, root, parent); this.preProcessXml(root); //解析前 对bean进行处理 留给子类实现 this.parseBeanDefinitions(root, this.delegate); //这里专门解析 this.postProcessXml(root); //解析后,对bean进行处理 留给子类实现 this.delegate = parent; }

通过上面的代码我们看到了处理流程,首先是对profile的处理,然后看是解析,可是当我们更加preProcessXml和postProcessXml的时候 发现里面是空的,,如果你有了解过设计模式 那么这就是模板方法模式,如果子类需要对加载bean 之前和加载 bean 之后进行处理 则子类需要继承DefaultBeanDefinitionDocumentReader
处理profile 标签

继承到web环境是,在web.xml 加入以下代码,
Spring.profile.activedev

从这里我们就可以看出 在配置文件中部署两套配置 来试用于测试环境和生产环境,这样就可以方便的进行切换
解析并且注册BeanDefinition
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)) { this.parseDefaultElement(ele, delegate); //对bean进行处理 } else { delegate.parseCustomElement(ele); //对bean进行处理 } } } } else { delegate.parseCustomElement(root); }}

在Springde Xml 的配置中有两大类Bean声明一种是默认的 如:

另一种就是自定义的

如果是自定以的Spring知道该怎么做,则采用this.parseDefaultElement方法进行解析,否则则采用 delegate.parseCustomElement(ele)方法 对自定义 命名空间进行解析,而判断是否默认命名空间还是自定义空间的办法是用 delegate.isDefaultNamespace(ele) 中的 node.getNamespaceURI() 来获取命名空间,并且于Spring中固定的命名空间进行对比http://www.springframework.org/schema/beans,如果一样则是默认的 否则 则不是
【Spring|Spring 源码分析之IOC容器的基本实现】对于默认标签解析于自定义标签解析 我们下次再说

    推荐阅读