Spring解析XML注册BeanDefinition流程

一、概述

1. 为什么需要理解XML配置解析?

我是一个刚交了一年社保的一年工作经验的小老弟,从大学刚接触软件开发到毕业正式入职所接触到JavaSE或JavaEE项目中,基本都会使用Spring作为项目的对象管理容器。尤其在大学期间,WEB项目使用Spring的时候基本都是通过配置applicationContext.xml全局配置文件,然后使用web.xml配置ContextLoaderListener加载这个全局配置文件去初始化容器。
早期版本的Spring,只能通过加载XML去启动配置文件,演变到现在也可以通过扫描类和注解去启动Spring容器,但Spring的本质和核心是不会变的。通过学习XML配置解析可以起到窥一斑而知全貌的效果,当你在研究扫描类和注解的时候会突然发现,大致的流程基本是很相似的。

2. BeanDefinition的注册有必要了解吗?

当你成功启动了一个Spring容器,有一个Bean配置的是懒加载,那么此时这个Bean在容器中是否存在?或者是以什么方式存在?
答案就是在一个需要被实例化的类还没有创建的时候,它在Spring中就是一个BeanDefinition,也就是这个Bean的定义,BeanDefinition包含了这个对象实例化所需要用到的所有信息。(详情可见之前的文章有描述过)
由此可见BeanDefinition注册是Spring容器的启动中非常重要的一环,BeanDefinition的注册不难,简单来讲就是Spring中有一个装BeanDefiniton的Map,当你扫描解析完XML的Bean标签之后就会注册到这个Map中。

二、ClassPathXmlApplicationContext解析XML

1. ClassPathXmlApplicationContext的解析XML入口方法

    // 方法在AbstractApplicationContext的709行左右
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        /**
         * 主要内容:配置信息到beanDefinition的转换过程
         * 模版设计模式
         * Spring中运用的最多的设计模式
         *      obtainFreshBeanFactory方法是一个模版方法
         *      refreshBeanFactory方法是一个钩子方法,需要子类去实现
         */
        refreshBeanFactory();

        return getBeanFactory();
    }

这个方法在容器的refresh()流程中被调用,ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 实际调用的是ClassPathXmlApplicationContext的父类AbstractApplicationContext的方法。(模版方法模式)
obtainFreshBeanFactory()方法也是一个模版方法,其中定义了一个钩子方法refreshBeanFactory(),refreshBeanFactory()方法在AbstractApplicationContext中是一个抽象方法,需要子类去实现。

protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

1.1 XML解析前创建工厂

经过查询容器类的继承结构,发现最终调用的是AbstractRefreshableApplicationContext的refreshBeanFactory()方法。该方法中不重要的掐头去尾已经省略了,主要有记个关键点和概念需要理解一下,就是BeanFactory是一个实例工厂,主要是用来管理Bean的,容器Context包含了BeanFactory。

    protected final void refreshBeanFactory() throws BeansException {
        // ...
        try {
            /**
             * 创建beanFactory
             * beanFactory:实例工厂,无论什么实例只要被spring管理,都在这个工厂里面,主要是bean的相关操作。
             * Context和beanFactory的关系:从属关系。  不重要理解即可
             */
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            /*
             * 容器重要属性设置: 
             *  1.是否允许同名bean
             *  2.是否允许循环依赖
             */
            customizeBeanFactory(beanFactory);
            /**
             *  解析XML,注册beanDefinition 重要:* * * * *
             */
            loadBeanDefinitions(beanFactory);
            // ...
        }
        // ...
    }

其中我们创建的工厂就是Spring默认的工厂,DefaultListableBeanFactory。

        protected DefaultListableBeanFactory createBeanFactory() {
        return new DefaultListableBeanFactory(getInternalParentBeanFactory());
    }

1.2 创建工厂后设置一些参数

在上述方法流程中会执行customizeBeanFactory(),这个方法会设置一些工厂的属性,是否允许同名bean存在和是否允许循环依赖。(看代码注释即可)

    protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
        /*
         * 是否允许同名的bean存在
         * 默认是不允许的
         */
        if (this.allowBeanDefinitionOverriding != null) {
            beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        /*
         * 是否允许循环依赖
         * 默认是允许的
         */
        if (this.allowCircularReferences != null) {
            beanFactory.setAllowCircularReferences(this.allowCircularReferences);
        }
    }

1.3 执行加载XML注册BeanDefinition的方法。

同样是在1.1的方法流程中,执行了loadBeanDefinitions(beanFactory);方法并传入当前创建的BeanFactory。
loadBeanDefinition(DefaultListableBeanFactory beanFactory)这个方法在AbstractRefreshableApplicationContext是一个抽象方法,也需要子类去实现它。经过查询其继承结构发现是AbstractXmlApplicationContext实现的。

    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
            throws BeansException, IOException;

在AbstractXmlApplicationContext抽象类,loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法主要是采用委托设计模式去实现的。 也就是说容器将XML的解析委托给了XmlBeanDefinitionReader类的实例,自身并没有详细的解析流程,将自身作为参数传入构造器,使得XmlBeanDefinitionReader类的实例可以再解析XML的过程中在工厂中注册BeanDefinition。

     protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        /**
         * 创建XML解析器,这里涉及到了委托设计模式
         * 委托设计模式:【专人专事】
         *      主类要持有委托类的引用,在实现服务时把具体实现的逻辑委托给委托类去实现。
         * 创建XML解析器
         * Create a new XmlBeanDefinitionReader for the given BeanFactory.
         */
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        /**
         * 使得XmlBeanDefinitionReader持有当前Spring容器的对象,这个很重要需要记住。
         * 把当前的Spring容器当作ResourceLoader注入到XmlBeanDefinitionReader
         * ClasspathXmlApplicationContext 类的继承链最顶端是一个 ResourceLoader
         */
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
        initBeanDefinitionReader(beanDefinitionReader);
        /**
         * 执行解析,解析XML为BeanDefinition过程很重要
         */
        loadBeanDefinitions(beanDefinitionReader);
    }
       // XmlBeanDefinitionReader 重载的 loadBeanDefinitions 方法,拿URL,直接委托。
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        // ...
        //没有多余的代码简单的条件直接委托给其他对象去执行,就是专人专事。
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
                 // 走这个方法  重要 : * * * * *
            reader.loadBeanDefinitions(configLocations);
        }
    }

2. 解析XML从AbstractBeanDefinitionReader开始

2.1 解析URL的心路历程

紧接上文执行reader.loadBeanDefinitions(configLocations);,实际调用的是AbstractBeanDefinitionReader类的loadBeanDefinitions(String... locations) 方法。loadBeanDefinitions(String... locations)方法又会循环遍历这个locations去挨个解析location。

    public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
        Assert.notNull(locations, "Location array must not be null");
        int count = 0;
        /**
         * 循环遍历解析各个路径下的xml文件
         */
        for (String location : locations) {
            count += loadBeanDefinitions(location);
        }
        return count;
    }

loadBeanDefinitions(String location)方法会调用另一个重载的方法,loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources)方法,只不过actualResources参数传的是null。

    public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
        /**
         * 根据地址解析XML文件
         */
        return loadBeanDefinitions(location, null);
    }

2.2 从解析URL到解析Resource的心路历程

下面代码先执行了 getResourceLoader() ,这个ResourceLoader就是容器本身,在创建XmlBeanDefinitionReader时候持有的容器对象。因为容器本身顶层实现了ResourcePatternResolver接口,所以可以通过getResources方法得到资源数组(Resource[])。
然后再次调用重载的遍历解析资源的loadBeanDefinitions(Resource... resources)方法。

    public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
        /**
         * resourceLoader在loadBeanDefinitions前被注入到reader对象中
         * 这个resourceLoader其实就是容器本身对象
         */
        ResourceLoader resourceLoader = getResourceLoader();
        // .......
        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                /**
                 * Spring.xml对象封装成一个Resource对象,Resource对象中有文件对象,文件流。
                 */
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                /**
                 * 解析这些Resource对象
                 */
                int count = loadBeanDefinitions(resources);
            }
        // .......
    }

转来转去,还没有结束,批量解析资源的loadBeanDefinitions遍历调用解析单个资源的loadBeanDefinitions方法。

    public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
        Assert.notNull(resources, "Resource array must not be null");
        int count = 0;
        for (Resource resource : resources) {
            /**
             * AbstractBeanDefinitionReader父类的方法
             * 模版方法
             */
            count += loadBeanDefinitions(resource);
        }
        return count;
    }

2.3 从解析Resource到解析InputStream的心路历程

上面遍历调用解析Resource的是,调用的是BeanDefinitionReader接口的这个方法

    int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;

实际执行的是XmlBeanDefinitionReader实现类的方法,然后又会将这个resource编码后再次调用重载的方法。

    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException 
       {
        /**
         * 实现类实现的钩子方法
         * resource -> Encoded 将流变成有编码的流
         */
        return loadBeanDefinitions(new EncodedResource(resource));
    }

我们来查看下加载有编码的流的方法是什么样子的。就是获取编码流中的InputStream然后继续去解析这个流。

    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        //......
        try {
            /**
             * 拿到文件流
             */
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                /*
                 * InputSource是XML文档解析的类 , JDK的类。专门做XML文档解析的一个类
                 */
                InputSource inputSource = new InputSource(inputStream);
                //......
                /**
                 * 加载Bean的定义信息
                 */
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
        //......
    }

2.4 Java 的 SAX 解析XML

承接2.3继续到了doLoadBeanDefinitions(InputSource inputSource, Resource resource)看见了曙光,终于是将inputSource和resource解析成了Document对象。提到Document对象我们都不会陌生,就是XML解析出的文档信息,里面包含了Node标签,也就是我们配置的Bean。
然后就是注册BeanDefinition的方法,在了解BeanDefinition注册之前,需要详细的了解一下doLoadDocument(InputSource inputSource, Resource resource) 这个方法的具体实现。

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
                //...........
            /**
             * xml文件流对象解析为document对象
             */
            Document doc = doLoadDocument(inputSource, resource);
            /**
             * 将document对象解析为BeanDefinition,并返回解析的数量
             */
            int count = registerBeanDefinitions(doc, resource);
                //...........
    }

这个方法的实现很长一串,我们点进loadDocument( ... ... )这个方法。

    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        /**
         * 实际解析XML InputSource的方法,解析成一个Document对象
         */
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }

可以看着这是一个SAX解析的常规流程,创建工厂,创建builder,解析输入流,然后返回Document。

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

        /**
         * XML解析封装document对象常规流程 SAX解析常用
         * 1.创建工厂
         * 2.创建builder
         * 3.解析输入流
         */
        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isTraceEnabled()) {
            logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        /**
         * 解析成Document对象并返回
         */
        return builder.parse(inputSource);
    }

到此为止将一个XML解析成Document的内容就结束了,当然Document还需要进一步解析成一个个Element并提取每个元素的信息如果是Bean的话就组册BeanDefinition。

3. 解析Document,注册BeanDefinition

3.1 开始解析Document

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
                //...........
            /**
             * xml文件流对象解析为document对象
             */
            Document doc = doLoadDocument(inputSource, resource);
            /**
             * 将document对象解析为BeanDefinition,并返回解析的数量
             */
            int count = registerBeanDefinitions(doc, resource);
                //...........
    }

回到doLoadBeanDefinitions这个方法,执行完将Resource转换为Document后,着重看解析document并注册BeanDefinition的内容。下面我们看registerBeanDefinitions(doc, resource)方法具体实现。

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        /**
         * 委托设计模式:
         * 把解析的工作委托给BeanDefinitionDocumentReader对象
         * 已把xml -> document ,继续委托给DocumentReader解析document
         */
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        int countBefore = getRegistry().getBeanDefinitionCount();

        /**
         * 解析Document对象并注册BeanDefiniton
         * 方法:registerBeanDefinitions(args1,args2)
         * 参数:Document doc, XmlReaderContext readerContext
         */
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

可以看到我们解析Document对象依旧是吧这个解析的过程委托给了BeanDefinitionDocumentReader类的实例。
上述执行流程中关注下 createReaderContext(resource) ,这个方法是返回XmlReaderContext类的实例,并且在构造实例的过程中,持有了当前的XmlBeanDefinitionReader的对象。之前我们提到过XmlBeanDefinitionReader持有了当前容器的引用,所以在createReaderContext(resource)方法的返回值XmlReaderContext实例中也间接持有了当前容器的引用,它可以去注册BeanDefinition。
继续看documentReader.registerBeanDefinitions这个调用方法的具体实现。

public interface BeanDefinitionDocumentReader {
    void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
            throws BeanDefinitionStoreException;

}

来到了BeanDefinitionDocumentReader接口,最终调用的是下面的DefaultBeanDefinitionDocumentReader实现类的方法。可以看到方法将readerContext的实例持有到自身的对象之中。并开始解析根结点。

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        /**
         * 把Document根结点 (root) 传进去
         */
        doRegisterBeanDefinitions(doc.getDocumentElement());
    }

3.2 具体Element标签的解析

来到根节点Element的具体解析实现,其实可以忽略大部分的内容,我们只需要看parseBeanDefinitions(root, this.delegate)这个主要的解析标签方法就好了,因为获取delegate,这个delegate是用来解析自定义标签的,自定义标签解析内容很多,所以需要总结一篇详细的文章来描述自定义标签的解析,这里就暂时忽略了。

    protected void doRegisterBeanDefinitions(Element root) {
        BeanDefinitionParserDelegate parent = this.delegate;
        /**
         * 主要是获取delegate,用来委托给第三方解析起解析自定义标签
         */
        this.delegate = createDelegate(getReaderContext(), root, parent);
        if (this.delegate.isDefaultNamespace(root)) {
            //  ...... 省略多行代码
        }
        /**
         * 预处理模版方法 暂时无具体实现
         */
        preProcessXml(root);
        /**
         * 主要看这个方法,标签的具体解析过程
         */
        parseBeanDefinitions(root, this.delegate);
        /**
         * 后处理模版方法 暂时无具体实现
         */
        postProcessXml(root);
        this.delegate = parent;
    }

这里他首先会获取根结点中的所有子结点,也就是我们配置的Bean,Import等传统标签,当然也有context-componentsacn等自定义的标签。这里我们主要看默认的传统标签解析。自定义标签解析需要详细的总结一篇文章来描述。

    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解析
                         */
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }else {delegate.parseCustomElement(root);}
    }

默认标签中我们常用的也就是<bean /> <import />标签,最重要的是<bean />标签,这个是我们最最常用的。我给标了重要程度五颗星。进入processBeanDefinition(ele, delegate);这个方法详细的看下。

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            /**
             * import标签的解析,重要程度:*
             */
            importBeanDefinitionResource(ele);
        }
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            /**
             * alias标签的解析 别名标签,重要程度:*
             */
            processAliasRegistration(ele);
        }
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            /**
             * bean标签的解析,重要程度:* * * * *
             */
            processBeanDefinition(ele, delegate);
        }
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            /**
             * recurse ,不重要 外层的beans
             */
            doRegisterBeanDefinitions(ele);
        }
    }

到这里我们看其实我之前看源码的注释描述的还是很多的,可见这里面涉及到了很多知识点,但是不重要,我们只关注 Element 具体是如何解析的,并且这个解析好的BeanDefinitionHolder注册到了容器的哪个地方,有没有什么业务规则。

3.2.1 大致描述processBeanDefinition方法的实现:
  1. 将element解析成BeanDefinitionHolder
  2. 装饰这个BeanDefinitionHolder如果需要的话
  3. 注册这个BeanDefinitionHolder
    接下来会把这三点拆分成3个小的点来描述。
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        /**
         * 方法:parseBeanDefinitionElement
         * 解析document封装成beanDefinition
         *
         * 重要程度:* * * * *
         */
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            /**
             * 没吊用,但需要学习设计思想。
             *
             * 装饰者设计模式,加上SPI(service provider interface)的设计思想SPI(Mybatis,Spring,Dubbo的SPI扩展)
             * SPI用来解耦,扩展的设计思想,在不改变原有代码的前提下进行开发,实现热插拔。和策略模式有点像
             * SPI简单来讲就是加载配置文件通过配置文件类中的类路径信息,再不修改核心代码的前提下扩展功能。
             *
             * 内容:
             * 1.namespace uri和解析类建立映射关系
             * 2.解析类实现统一接口完成多态
             * 3.beandefinition的不断包装/装饰
             *
             * 不使用构造器和属性注入,在bean标签中使用前缀属性 p: c:进行注入 ,在xmlns中加入schema/p schema/c 约束
             *
             * 重要程度:*
             */
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

            try {

                /**
                 * 逻辑很简单,构建 别名 -> beanname -> beandefinition 的映射。
                 *
                 * 完成document到BeanDefinition对象的转换,对BeandDefinition对象进行缓存注册
                 * 重要程度: * * *
                 */
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error("Failed to register bean definition with name '" +
                        bdHolder.getBeanName() + "'", ele, ex);
            }
            // Send registration event.
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }
    }

3.3 <bean /> 标签元素解析成BeanDefinitionHolder的过程

先进入 delegate.parseBeanDefinitionElement(ele);的方法看一下,依然是重载外面包个壳。

    @Nullable
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
        return parseBeanDefinitionElement(ele, null);
    }

再次进入parseBeanDefinitionElement(ele, null);这个方法。

3.3.1 parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) 外层提取信息具体实现
    @Nullable
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
        // 解析过程不复杂,但是解析项比较多。BeanDifinition的属性比较多。
        /**
         * 参数提取:提取标签的 ID 和 name
         */
        String id = ele.getAttribute(ID_ATTRIBUTE);
        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

        /**
         * 标签name  = ","或";"分割的字符串 解析别名列表
         */
        List<String> aliases = new ArrayList<>();
        if (StringUtils.hasLength(nameAttr)) {
            String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            aliases.addAll(Arrays.asList(nameArr));
        }
        String beanName = id;
        if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
            beanName = aliases.remove(0);
            if (logger.isTraceEnabled()) {
                logger.trace("No XML 'id' specified - using '" + beanName +
                        "' as bean name and " + aliases + " as aliases");
            }
        }
        /**
         * 检查beanname的唯一性
         */
        if (containingBean == null) {
            checkNameUniqueness(beanName, aliases, ele);
        }
        /**
         * 解析这个元素 ele 剩余的全部的属性
         * 返回 名为beanName的 AbstractBeanDefinition 对象
         * 详细的解析过程:* * * * *
         */
        AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
        if (beanDefinition != null) {
            // ............. 省略了一大堆代码
            String[] aliasesArray = StringUtils.toStringArray(aliases);
            /**
             * BeanDefinition再次进行一个包装 -> BeanDefinitionHolder
             *
             * BeanDefinitionHolder是BeanDefinition,名称,别名的组装
             */
            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
        }

        return null;
    }

代码概述:

  1. 先提取了标签的id和name,如果name的配置了多个别名还要拆一下,如果id是空的直接拿第一个别名用一下。
  2. 因为当前参数containingBean是空的,所以要检测一下Bean的唯一性,也就是beanName和别名的唯一性。方法里面内容不多很好理解,自己点进去看下就好了。
  3. parseBeanDefinitionElement方法是,具体的提取信息创建BeanDefinition,提取属性设置属性。(这个方法最重要)
  4. 包装成BeanDefinitionHolder返回,BeanDefinitionHolder和BeanDefinition区别就是BeanDefinitionHolder包装了除了类定义外的beanName和别名数组。
3.3.2 parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) 属性设置具体实现

进入 parseBeanDefinitionElement(ele, beanName, containingBean); 方法查看具体实现。

@Nullable
    public AbstractBeanDefinition parseBeanDefinitionElement(
            Element ele, String beanName, @Nullable BeanDefinition containingBean) {
        this.parseState.push(new BeanEntry(beanName));
        /*
         * 获取它的class属性,如果有。
         */
        String className = null;
        if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
            className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
        }
        /*
         * 获取它的parent属性,如果有。
         */
        String parent = null;
        if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
            parent = ele.getAttribute(PARENT_ATTRIBUTE);
        }
        try {
            /**
             * 创建一个GenericBeanDefinition对象并设置父对象id
             */
            AbstractBeanDefinition bd = createBeanDefinition(className, parent);
            /**
             * 重要: * * * * *
             * 解析bean标签的属性把属性设置到对象中
             */
            parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
            bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

            /*
             * Bean标签的Meta子标签的解析,没什么用
             */
            parseMetaElements(ele, bd);
            /**
             * lookup-method  demo05
             * 替代某方法的返回值。 使用代理实现。
             * 重要程度:* *
             */
            parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
            /**
             * replace-method demo06
             * arg-type子标签区分重载参数类型
             * 在不改变原有代码的基础上进行增强,可以用AOP替代.
             * 重要程度:* *
             */
            parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
            /**
             * 解析Bean中的constructor-arg标签
             * 重要程度:* *
             */
            parseConstructorArgElements(ele, bd);
            /**
             * 解析Bean中的property, 属性注入可以用@Value替代
             * 重要程度:* *
             */
            parsePropertyElements(ele, bd);
            /**
             * @Qualifier指定注入哪个bean
             * 重要程度:* *
             */
            parseQualifierElements(ele, bd);
            // .............省略
            /**
             * 整个bean标签就解析完了
             */
            return bd;
        }
        // .............省略
    }

具体实现内容概述:

  1. 获取标签中的class属性
  2. 获取标签中的parent属性
  3. 基于class和parent先创建一个BeanDefinition对象
  4. parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);这个方法里面会对BeanDefinition设置很多属性,如scop属性、abstract属性、lazy属性、autowireMode属性、depends-on属性、autowire-candidate属性、init-method属性、destory-method属性、factory-bean和factory-method属性等。如果对于属性含义不了解看下我之前等 《快速理解Spring加载流程》的那个文章,里面有对BeanDefinition的属性的一些描述。
  5. <meta />子标签解析,解析后的键值对会存储到BeanDefinition的attributes这个Map中。如果需要用到<meta />这个值,需要先获取BeanDefinition。
  6. lookup-method这个属性的作用是,如果创建当前这个类的Bean,会给这个类的某一个方法塞一个返回值。如下案例就是调用ShowSixClass类的getPeople方法,我返回woman。(就是这么个作用,实际信息存储在BeanDefinition的MethodOverrides属性中)
    <bean id="people" class="com.jd.nlp.dev.muzi.spring5.exercise.demo05.ShowSixClass" >
        <!--简单理解就是 给某个方法塞返回值 体现出一种多态的方式-->
        <lookup-method name="getPeople" bean="woman" />
    </bean>
  1. replace-method这个属性就是增强某个方法,这个可以使用AOP去代替,但是还是弄个案例看一下。这个方法需要进行业务功能增强,但是又不希望在原来基础上修改,可以用 replaced-method标签。下述案例就是originClass这个Bean在调用method(String str)方法的时候会调用replaceClass的reimplement方法。(实际信息存储在BeanDefinition的MethodOverrides属性中)
//-----------配置--------------
    <bean id="replaceClass" class="com.jd.nlp.dev.muzi.spring5.exercise.demo06.ReplaceClass" />

    <bean id="originClass" class="com.jd.nlp.dev.muzi.spring5.exercise.demo06.OriginClass">
        <replaced-method name="method" replacer="replaceClass">
            <!-- 使用arg-type来区分重载的方法 -->
            <arg-type match="java.lang.String" />
        </replaced-method>
    </bean>
// ------------代码---------------
public class OriginClass {
    public void method(String param) {
        System.out.println();
        System.out.println("I am origin method! param = " + param);
        System.out.println();
    }
    public void method(List param) {
        System.out.println();
        System.out.println("I am origin method! param = " + param);
        System.out.println();
    }
}
public class ReplaceClass implements MethodReplacer {
    @Override
    public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("I am replace method -> reimplement -> begin");
        System.out.println("obj:"+ obj.toString());
        System.out.println("method:" + method.getName());
        System.out.println("args:" + args);
        System.out.println("I am replace method -> reimplement -> end");
        return null;
    }
}
  1. constructor-arg标签的内容最终会存储在 BeanDefinition的 ConstructorArgumentValues 属性中,用作实例化有参构造函数参数获取。
  2. 解析Bean中的property,在当前支持注解扫描的版本下,可以用@Value替代 。
  3. @Qualifier指定注入哪个bean

以上就是parseBeanDefinitionElement(ele, beanName, containingBean)方法的具体实现内容,其实梳理下来就很简单,无非就是提取<bean />标签中配置的参数嘛,然后都包装到BeanDefinition对应的属性当中,最终把这个BeanDefinition返回。
至此,一个Bean标签的基本信息就被解析完了。返回BeanDefinition并被包装成一个Holder对象,然后我们回到之前解析的主流程中。

3.4 BeanDefinitionHolder是否需要被装饰?

再次看一下解析一个传统<bean /> 标签主流程的代码。

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        /**
         * 方法:parseBeanDefinitionElement
         * 解析document封装成beanDefinition
         * 重要程度:* * * * *
         */
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            /**
             * 没吊用,但需要学习设计思想。
             * 装饰者设计模式,加上SPI(service provider interface)的设计思想SPI(Mybatis,Spring,Dubbo的SPI扩展)
             * SPI用来解耦,扩展的设计思想,在不改变原有代码的前提下进行开发,实现热插拔。和策略模式有点像
             * SPI简单来讲就是加载配置文件通过配置文件类中的类路径信息,再不修改核心代码的前提下扩展功能。
             * 内容:
             * 1.namespace uri和解析类建立映射关系
             * 2.解析类实现统一接口完成多态
             * 3.beandefinition的不断包装/装饰
             * 不使用构造器和属性注入,在bean标签中使用前缀属性 p: c:进行注入 ,在xmlns中加入schema/p schema/c 约束
             * 重要程度:*
             */
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            // ................................................
        }
    }

delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);装饰这个BeanDefinitionHolder如果需要的话?

3.4.1 什么是装饰?

在我理解,装饰就是不断的对一个对象进行包装,填充这个对象的可变的属性列表值,使得我们可以动态的去达到自己想要表达的内容。
有的装饰是通过继承不断的去修改最初始的内容,不断的扩充自己所拥有的东西。而在Spring的装饰中,其实就是通过 SPI(service provider interface)服务发现思想,去判断我这个Bean是否需要被装饰。

3.4.2 什么是SPI?

SPI是service provider interface的缩写,是一种服务发现机制。

Spring的SPI设计首先要理解什么是 " namespaceURI ",如下面代码所示 xmlns 后面配置的 "http://www.springframework.org/schema/beans" 就是 " namespaceURI "。xsi:schemaLocation里面配置的是你的自定义标签的具体Schema(自定义标签解析相关内容暂且不提)。

<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="decoratorBean" class="com.jd.nlp.dev.muzi.spring5.exercise.demo07.DecoratorBean"
          p:username="jack" p:password="123"

          c:age="12" c:sex="1"
    />
</beans>

我们看上面配置文件中bean标签中有两种很奇怪的属性 “p:” 和 “c:” ,其中 “p:” 配置和Property功能类似,“c:” 配置和construct-args子标签属性功能类似,但是在Spring中这种属性是如何解析的呢?

想要知道如何解析“p:”和“c:”,首先要打开spring-beans的这个jar包,找到META-INF目录,找到spring.handlers文件打开。这里面定义的就是“p:”和“c:”的解析类与namespaceURI的关系。

文件:META-INF/spring.handlers

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

回到之前调用装饰方法的入口,看一下bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);具体的方法是怎么实现的?
依然是包装了一下,然后调用到实际的实现方法中,首先他是获取标签Element元素的全部Node去遍历,查看是否需要装饰。主要的方法是 decorateIfRequired 方法。

    public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
        return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
    }
    public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
            Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {
        BeanDefinitionHolder finalDefinition = definitionHolder;
        /**
         * 通过元素的属性来装饰
         */
        NamedNodeMap attributes = ele.getAttributes();
        // 循环
        for (int i = 0; i < attributes.getLength(); i++) {
            Node node = attributes.item(i);
            // 装饰如果需要
            finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
        }
        /**
         * 通过子标签装饰
         */
        NodeList children = ele.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
            }
        }
        return finalDefinition;
    }

这个方法我们可以看到,它是在看当前Node是否能得到NamespaceURI,得到了NamespaceURI就会通过NamespaceURI获取NamespaceHandler。然后调用handler.decorate去装饰这个beanDefinition。

    public BeanDefinitionHolder decorateIfRequired(
            Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {

        // node 就是 p:xxxx="xxxx"  demo07
        // 根据node获取node的命名空间,形如:http://www.springframework.org/schema/p
        String namespaceUri = getNamespaceURI(node);

        if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {

            /**
             * SPI服务发现思想,通过URI获取spring.handlers配置的处理类
             *
             * resolve(namespaceUri)方法中有详细的解析过程
             */
            NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);

            if (handler != null) {
                /**
                 * 实际的解析类,调用装饰方法。 可以理解为 handler 是一个装饰者, beanDefinition是被装饰者。
                 */
                BeanDefinitionHolder decorated =
                        handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
                // ................
            }
            // ................
        }
        return originalDef;
    }

看下resolve具体实现,是如何获取到NamespaceHandler的?

  1. getHandlerMappings获取到所有jar包中的spring.handlers文件中的namespaceURI和其对应的处理类的类路径,放入一个Map中。
  2. 反射这个类,并创建这个累的实例,得到namespaceHandler。
  3. 调用namespaceHandler的init方法。
  4. 把实例好的对象和namespaceURI映射上
  5. 返回这个namespaceHandler对象。

这样上文通过resolve获得的namespaceHandler对象,就是spring.handlers配置的当前namespaceURI对应的解析类的实例。这样Spring就可以通过namespaceHandler调用decorate方法进行装饰了。

    public NamespaceHandler resolve(String namespaceUri) {
        /**
         * 加载"META-INF/spring.handlers"文件,建立URI和处理类的映射关系
         *
         * uri和类的映射关系,通过uri唯一找到一个类
         *
         * 方法:getHandlerMappings
         * 重要程度:* * *
         */
        Map<String, Object> handlerMappings = getHandlerMappings();

        // 根据URI就可以找到唯一的处理类(字符串)
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        if (handlerOrClassName == null) {
            return null;
        }
        else if (handlerOrClassName instanceof NamespaceHandler) {
            return (NamespaceHandler) handlerOrClassName;
        }
        else {
            // 处理类(字符串)反射
            String className = (String) handlerOrClassName;
            try {
                /**
                 * 反射这个类
                 */
                Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
                if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                    // ......................
                }
                /**
                 * 基于类对象来实例化
                 * 备注:所有处理类必须继承NamespaceHandler,实现多态。
                 * 例如:
                 *   SimpleConstructorNamespaceHandler implements NamespaceHandler
                 *
                 *   所有spring.handlers这些命名解析类都有一个特点是必须实现NamespaceHandler接口,来实现多态
                 */
                NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);

                // 调用处理类初始化方法
                namespaceHandler.init();

                // 替换映射关系key对应的值
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
        // ......................
        }
    }

简单看下"p:"属性对应的namespaceURI对应的解析类把,这是p的spring.handlers的配置。对应的解析类是SimplePropertyNamespaceHandler。

http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler

这是p的解析类的代码,SimplePropertyNamespaceHandler类的 init 方法没有具体的内容,但是decorate有装饰的详细逻辑,就不解读了。就是往BeanDefinition对应的属性塞值。和之前那波设置属性的操作差不多。"p:" 对应之前的逻辑的就是"property"子标签属性设置。

public class SimplePropertyNamespaceHandler implements NamespaceHandler {
    private static final String REF_SUFFIX = "-ref";
    @Override
    public void init() {
    }
    /**
     * p:实际对应的装饰方法
     */
    @Override
    public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
        /**
         * 被装饰对象 definition
         * 主要内容是 解析  p:xxxx="xxxx" 内容,封装到属性 MutablePropertyValues 列表元素中去
         */
        if (node instanceof Attr) {
            Attr attr = (Attr) node;
            String propertyName = parserContext.getDelegate().getLocalName(attr);
            String propertyValue = attr.getValue();
            MutablePropertyValues pvs = definition.getBeanDefinition().getPropertyValues();
            if (pvs.contains(propertyName)) {
                parserContext.getReaderContext().error("Property '" + propertyName + "' is already defined using " +
                        "both <property> and inline syntax. Only one approach may be used per property.", attr);
            }
            if (propertyName.endsWith(REF_SUFFIX)) {
                propertyName = propertyName.substring(0, propertyName.length() - REF_SUFFIX.length());
                pvs.add(Conventions.attributeNameToPropertyName(propertyName), new RuntimeBeanReference(propertyValue));
            }
            else {
                /**
                 * 把属性内容加入到definition的MutablePropertyValues列表中
                 * 这样一种反复的对definition进行装饰/包装,体现了装饰者设计模式的感觉。
                 *
                 * 具体谁是装饰者已经不重要了,对beanDefinition已经进行反复的修改了。
                 */
                pvs.add(Conventions.attributeNameToPropertyName(propertyName), propertyValue);
            }
        }
        return definition;
    }

}

3.5 BeanDefinition注册的位置在哪里?

回归到之前解析<bean >标签的主流程中, BeanDefinitionReaderUtils.registerBeanDefinition这个方法就是在注册BeanDefinition。
getReaderContext() 获取到的就是之前从第一次委托对象持有的容器本身的引用,一直被间接的持有着这个对象。容器本身是含有BeanFactory和注册器的,所以可以获取到BeanDefinitionRegistry。
BeanDefinitionRegistry其实就是DefaultListableBeanFactory,BeanDefinitionRegistry是个接口。

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        //.........................
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            //....................
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            // ................................................
        }
    }
3.5.1 看下BeanDefinitionReaderUtils.registerBeanDefinition具体实现:(注册BeanDefinition至特定容器)
  1. BeanName和BeanDefinition注册构建映射关系
  2. Alias和BeanName注册构建映射关系

可以理解,我们可以通过BeanName快速的找到BeanDefinition,当然也可以通过别名,找到BeanName间接的找到BeanDefinition。

    public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {
        /**
         * BeanName和BeanDefinition注册构建映射关系
         * 重要:* * *
         */
        String beanName = definitionHolder.getBeanName();
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
        /**
         * Alias和BeanName注册构建映射关系
         */
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            for (String alias : aliases) {
                registry.registerAlias(beanName, alias);
            }
        }
    }
3.5.2 看下registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());具体实现:(DefaultListableBeanFactory类的实现方法)
  1. 代码有点长该省略的都省略了
  2. 装beanDefinition的容器是 beanDefinitionMap
  3. 装beanName的集合是 beanDefinitionNames

以上2和3需要牢记,玩Spring源码debug的时候需要去这里找内容。

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {
        /**
         * 重要代码最下方
         */
        // .......................省略一万行
        BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
        if (existingDefinition != null) {
            // .......................省略一万行
        }
        else {
            // .......................省略一万行
            else {
                /**
                 * 把BeanDefinition缓存到Map中
                 */
                this.beanDefinitionMap.put(beanName, beanDefinition);
                /**
                 * 把 beanname 放到 BeanDefinitionNames 这个List中,在Bean实例化时需要使用到该List。
                 * 该List包含所有的beanDefinition的名称
                 */
                this.beanDefinitionNames.add(beanName);
                this.manualSingletonNames.remove(beanName);
            }
            this.frozenBeanDefinitionNames = null;
        }
              // .......................
    }
3.5.3 看下registry.registerAlias(beanName, alias);方法的具体实现:

GenericApplicationContext.class

    public void registerAlias(String beanName, String alias) {
        /**
         * 构建别名和 beanName的映射关系
         */
        this.beanFactory.registerAlias(beanName, alias);
    }

SimpleAliasRegistry.class

    public void registerAlias(String name, String alias) {
        //  ................... 
        /**
         * 映射关系在最下面
         */
        synchronized (this.aliasMap) {
            //  ................... 省略一万行
            else {
                String registeredName = this.aliasMap.get(alias);
                //  ................... 省略一万行

                /**
                 * 装的是别名和beanName【id的名称】的映射关系
                 * 别名取对象:
                 *      别名 - beanName 有映射关系
                 *      beanName -  beanDefinition 有映射关系
                 *
                 *   总体来说如果通过别名找beanDefinition需要二级映射
                 */
                this.aliasMap.put(alias, name);
                //  ................... 
            }
        }
    }

可以看到 alias 和 beanName的映射关系是在 aliasMap 中存储的。最外层是循环别名数组,以当前item 别名为key,以beanName为value一对一对存的。所以通过任意一个定义好别名都可以找到对应的beanName。

至此,ClassPathXmlApplicationContext 的 解析XML注册BeanDefinition流程就梳理完了。梳理的很难受,模版设计模式扩展性不错,就是跳来跳去的,抓耳挠腮,看湿了 ... ...

三、总结

简单总结一下,虽然微服务的大环境下,ClassPathXmlApplicaitionContext容器在我们日常的代码中越来越少的去使用了,但是万变不离其宗,Spring不论再怎么演变,初始的结构就是这样,以后只能是扩展和兼容,相似的功能还会复用之前的代码。所以吃透一套流程,待我们分析注解配置启动容器的时候,也是小事一桩。

就目前来看BeanDefinition的这些属性,有一些我们是基本不会用到的,就比如lookup-method,init-method(实现InititalizeBean接口,@PostConstruct可以替代),factory-bean(实现Factorybean接口就是将 getObject()方法结果放入Spring的factoryBeanObjectCache这个容器里,我们根据类实际的beanName获得的bean其实是getObject()方法返回的bean,如果要获取类真正的实例Bean,需要在beanName前加个&符号,这种机制目前确实不知道有什么用。

总之,源码内容不要过分的深究,要一遍一遍的读,先从整体角度去考虑,然后针对细节做深化梳理,我总结的Spring相关内容也是基于以广度为先,按照流程的先后在细致的对每一个我想知道的知识点进行深度梳理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351