zoukankan      html  css  js  c++  java
  • spring源码解析2--容器的基本实现

    spring的主要特性是IOC,实现IOC的关键是bean,而更关键的是如何bean的管理容器,也就是BeanFactory,本文的目标是弄清楚BeanFactory具体是怎么样的存在。

    先看下最简单的获取bean的案例,代码如下:

    1 public static void main(String[] args){
    2         BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-beans.xml"));
    3         User user = (User) factory.getBean("user");
    4         System.out.println(JSON.toJSON(user).toString());
    5     }

    首先是读取spring的配置文件,创建BeanFactory实例,如何直接从BeanFactory实例中获取指定名称的bean。接下来就从这几行简单的代码入手,分析下BeanFactory。

    BeanFactory实例是通过XmlBeanFactory来创建的,很明显XmlBeanFactory是BeanFactory的子类,继承关系图如下:

    那么先就从XmlBeanFactory的构造方法开始,源码如下:

     1 public class XmlBeanFactory extends DefaultListableBeanFactory {
     2 
     3     private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
     4 
     5     public XmlBeanFactory(Resource resource) throws BeansException {
     6         this(resource, null);
     7     }
     8 
     9     public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    10         super(parentBeanFactory);
    11         this.reader.loadBeanDefinitions(resource);
    12     }
    13 
    14 }

    第5行的构造方法调用第9行的构造方法,首先是执行父类的构造方法,然后执行XmlBeanFefinitionReader的loadBeanFefinitions(resource)方法,这个方法显然就是加载bean配置文件的方法。接下来跟踪查看,源码如下:

    1     @Override
    2     public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    3         return loadBeanDefinitions(new EncodedResource(resource));
    4     }

    通过EncodeResource来封装Resource,在调用重载的方法

     1 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
     2         Assert.notNull(encodedResource, "EncodedResource must not be null");
     3         //校验Resource参数
     4         if (logger.isInfoEnabled()) {
     5             logger.info("Loading XML bean definitions from " + encodedResource.getResource());
     6         }
     7 
     8         Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
     9         if (currentResources == null) {
    10             currentResources = new HashSet<>(4);
    11             this.resourcesCurrentlyBeingLoaded.set(currentResources);
    12         }
    13         if (!currentResources.add(encodedResource)) {
    14             throw new BeanDefinitionStoreException(
    15                     "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    16         }
    17         try {
    18             //获取Resource的InputSteam流
    19             InputStream inputStream = encodedResource.getResource().getInputStream();
    20             try {
    21                 //通过InputSteam构造InputSource
    22                 InputSource inputSource = new InputSource(inputStream);
    23                 if (encodedResource.getEncoding() != null) {
    24                     inputSource.setEncoding(encodedResource.getEncoding());
    25                 }
    26                 //最终执行doLoadBeanDefinitions方法
    27                 return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    28             }
    29             finally {
    30                 inputStream.close();
    31             }
    32         }
    33         catch (IOException ex) {
    34             throw new BeanDefinitionStoreException(
    35                     "IOException parsing XML document from " + encodedResource.getResource(), ex);
    36         }
    37         finally {
    38             currentResources.remove(encodedResource);
    39             if (currentResources.isEmpty()) {
    40                 this.resourcesCurrentlyBeingLoaded.remove();
    41             }
    42         }
    43     }

    这次封装的EncodedResource作用主要是对XML配置文件的编码格式进行处理,然后最终执行了doLoadBeanDefinitions方法

     1     protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
     2             throws BeanDefinitionStoreException {
     3         try {
     4             //通过InputSource和Resource得到一个Document对象,然后执行注册bean的方法
     5             Document doc = doLoadDocument(inputSource, resource);
     6             return registerBeanDefinitions(doc, resource);
     7         }
     8         catch (BeanDefinitionStoreException ex) {
     9             throw ex;
    10         }
    11         catch (SAXParseException ex) {
    12             throw new XmlBeanDefinitionStoreException(resource.getDescription(),
    13                     "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
    14         }
    15         catch (SAXException ex) {
    16             throw new XmlBeanDefinitionStoreException(resource.getDescription(),
    17                     "XML document from " + resource + " is invalid", ex);
    18         }
    19         catch (ParserConfigurationException ex) {
    20             throw new BeanDefinitionStoreException(resource.getDescription(),
    21                     "Parser configuration exception parsing XML from " + resource, ex);
    22         }
    23         catch (IOException ex) {
    24             throw new BeanDefinitionStoreException(resource.getDescription(),
    25                     "IOException parsing XML document from " + resource, ex);
    26         }
    27         catch (Throwable ex) {
    28             throw new BeanDefinitionStoreException(resource.getDescription(),
    29                     "Unexpected exception parsing XML document from " + resource, ex);
    30         }
    31     }

    该方法只有两行有效逻辑代码,首先是加载XML文件得到一个Document对象,然后根据Document对象来注册Bean信息,一步步来看,先看如何生成Document对象。

    1 private DocumentLoader documentLoader = new DefaultDocumentLoader();
    2 
    3 protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    4         return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
    5                 getValidationModeForResource(resource), isNamespaceAware());
    6     }

    调用了DocumentLoader的实例来进行加载,DocumentLoader接口的loadDocument方法

     1 public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
     2                                  ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
     3 
     4         //这里显然是工厂模式+建造者模式,先创建DocumentBuilder工厂,如何得到DocumentBuilder,最终执行parse方法解析xml配置文件得到Document对象
     5         DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
     6         if (logger.isDebugEnabled()) {
     7             logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
     8         }
     9         DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    10         return builder.parse(inputSource);
    11     }
     1 public Document parse(InputSource is) throws SAXException, IOException {
     2         if (is == null) {
     3             throw new IllegalArgumentException(
     4                     DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN,
     5                             "jaxp-null-input-source", null));
     6         }
     7         if (fSchemaValidator != null) {
     8             if (fSchemaValidationManager != null) {
     9                 fSchemaValidationManager.reset();
    10                 fUnparsedEntityHandler.reset();
    11             }
    12             resetSchemaValidator();
    13         }
    14         //上面的代码都是校验,核心是下面的三行,通过DomParser来获取Document对象
    15         domParser.parse(is);
    16         Document doc = domParser.getDocument();
    17         domParser.dropDocumentReferences();
    18         return doc;
    19     }

    解析配置文件得到了Document对象,接下来就是通过Document来注册beanl了。回到XmlBeanDefinitionReader的RegisterBeanDefinitions方法

     1 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
     2         //创建BeanDefinitionDocumentReader实例
     3         BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
     4         //统计当前已经加载过的BeanDefinition个数
     5         int countBefore = getRegistry().getBeanDefinitionCount();
     6         //加载及注册bean
     7         documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
     8         //得到本次加载的BeanFinition个数
     9         return getRegistry().getBeanDefinitionCount() - countBefore;
    10     }

    核心是BeanDefinitionDocumentReader接口的registerBeanDefinitions方法,代码如下:

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

    首先是获取Document的root,然后将root元素传递给下面的方法继续执行,这里的root元素就是配置文件中的<beans>标签

     1 protected void doRegisterBeanDefinitions(Element root) {
     2         //专门处理解析
     3         BeanDefinitionParserDelegate parent = this.delegate;
     4         this.delegate = createDelegate(getReaderContext(), root, parent);
     5 
     6         if (this.delegate.isDefaultNamespace(root)) {
     7             //处理<beans>标签中的profile属性
     8             String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
     9             if (StringUtils.hasText(profileSpec)) {
    10                 String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
    11                         profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
    12                 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
    13                     if (logger.isInfoEnabled()) {
    14                         logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
    15                                 "] not matching: " + getReaderContext().getResource());
    16                     }
    17                     return;
    18                 }
    19             }
    20         }
    21 
    22         preProcessXml(root);//代码为空(模板方法设计模式:提供给子类实现,如果需要在bean的解析前后做一些处理的话)
    23         parseBeanDefinitions(root, this.delegate);//Bean的解析
    24         postProcessXml(root);//代码为空
    25 
    26         this.delegate = parent;
    27     }

    发现bean的注册方法应该是parseBeanDefinitions方法,继续往下

     1  protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
     2         if (delegate.isDefaultNamespace(root)) {
     3             //获取<beans>标签的子节点也就是所有的<bean>标签
     4             NodeList nl = root.getChildNodes();
     5             for (int i = 0; i < nl.getLength(); i++) {
     6                 Node node = nl.item(i);
     7                 if (node instanceof Element) {
     8                     Element ele = (Element) node;
     9                     if (delegate.isDefaultNamespace(ele)) {
    10                         //如果是默认命名空间的bean
    11                         parseDefaultElement(ele, delegate);
    12                     }
    13                     else {
    14                         //如果是自定义命名空间的bean
    15                         delegate.parseCustomElement(ele);
    16                     }
    17                 }
    18             }
    19         }
    20         else {
    21             //自定义命名空间的bean
    22             delegate.parseCustomElement(root);
    23         }
    24     }

    这里是逻辑很清楚,获取root下的子节点遍历,判断是否是默认命名空间的元素来分别处理。判断是否是默认命名空间的方式是取Node的namespanceUrl来和默认命名空间的地址进行比较,默认地址为:

    public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";

    像<bean id="userService" class="com.test.UserService">这种写法就是默认标签,而如<tx:annotation-driven>这种就是自定义的写法。

    总结:本文从BeanFactory的初始化开始,解析XML配置文件,生成Docuemnt对象,解析Document中的标签,按标签的类型来进行区分解析,而两种的解析方式截然不同,接下来就分别解析。

  • 相关阅读:
    4.关于QT中的QFile文件操作,QBuffer,Label上加入QPixmap,QByteArray和QString之间的差别,QTextStream和QDataStream的差别,QT内存映射(
    PlSql加入数据库链接
    UserScan的处理流程分析
    第八十八题(金山笔试题,字符串移动)
    4Sum_leetCode
    LeetCode Add Binary
    Hibernate or JPA Annotation中BLOB、CLOB注解写法
    配置Nginx防止直接用IP訪问Webserver
    Java集合系列之TreeMap源代码分析
    使用Application Loader上传APP流程解读[APP公布]
  • 原文地址:https://www.cnblogs.com/jackion5/p/10780568.html
Copyright © 2011-2022 走看看