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 的实现类 DocumentBuilderFactoryImpl

        DocumentBuilderFactory 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.active
  dev

从这里我们就可以看出 在配置文件中部署两套配置 来试用于测试环境和生产环境,这样就可以方便的进行切换

解析并且注册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 源码分析之IOC容器的基本实现)