spring容器<bean>标签解析之自定义标签

前面我们知道了spring是如何解析bean标签的子标签如 meta、lookup-method、replaced-method、constructor-arg、property以及qualifier等,我们都是一一做了简答的解读,还有一种自定义类型的标签,接下来我们来看spring对自定义标签的解析过程,首先我们回到解析默认标签的方法的起始入口:DefaultBeanDefinitionDocumentReader#processBeanDefinition这里是解析beanDefinition的入口,其中包括我们自定义标签的解析过程

  • 首先是构建一个BeanDefinitionHolder的实例,在该实例不为null时去解析自定义标签
BeanDefinitionHolder为 name 和 alias 的 BeanDefinition 对象
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
若不为null
if (bdHolder != null) {
对自定义标签解析(因为我们可能对该标签有自己定义的属性)
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

在上述的代码中我们发现真正解析自定义标签的入口是:

delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

找到方法入口了,我们接下来看真正的解析的过程:

 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;

    // Decorate based on custom attributes first.
    //获取所有的属性
    NamedNodeMap attributes = ele.getAttributes();
    //遍历所有的属性,并寻找是否可以装饰的属性
    for (int i = 0; i < attributes.getLength(); i++) {
        Node node = attributes.item(i);
        finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
    }

    // Decorate based on custom nested elements.
    //获取所有的子元素
    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;
}

这就是解析的过程,简单的看一下过程:

  • 首先是对于BeanDefinitionHolder的初始化过程
  • 首先获取元素的所有的属性
BeanDefinitionHolder finalDefinition = definitionHolder;
  • 接着遍历属性,并判断是否有可以装饰的属性
  • 如果有,通过其decorateIfRequired()进行装饰
装饰属性的过程
public BeanDefinitionHolder decorateIfRequired(
        Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
    //获取自定义的命名空间
    String namespaceUri = getNamespaceURI(node);
    //对于默认标签的默认标签进行过滤
    if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
        //根据命名空间去获取对应的处理器
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler != null) {
            //进行装饰
            BeanDefinitionHolder decorated =
                    handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
            if (decorated != null) {
                return decorated;
            }
        }
        else if (namespaceUri.startsWith("http://www.springframework.org/")) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
        }
        else {
            // A custom namespace, not to be handled by Spring - maybe "xml:...".
            if (logger.isDebugEnabled()) {
                logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
            }
        }
    }
    return originalDef;
}

这是对属性装饰的详细过程,先说完所有的说完后我们接着更详细的看

  • 接着获取所有的子元素
NodeList children = ele.getChildNodes();
  • 遍历所有的子元素,装饰可以装饰的子元素

装饰子元素的过程跟装饰属性的过程是一致的,接下来我们详细的介绍下自定义标签的解析过程,我们知道我们所有的操作都是从配置到资源的封装再到转换成Document的过程,随之而来的是提取document里的各种元素的过程,并展开了对提取到的元素进行解析并装饰,由于spring中的标签有默认和自定义两种,spring对其分别解析过程,首先我们看一下自定义标签的使用过程:

  • 首先需要创建一个拓展的组件.
  • 接着是定义一个xsd文件描述组件的内容.
  • 接着是创建一个实现AbstractSingleBeanDefinitionParser 接口的类,用来解析xsd文件的定义和组件的定义.
  • 定义一个Handler继承NamespaceHandlerSupport 抽象类 ,其目的是将组件注册到spring的容器中.
  • 编写 spring.handlers和spring.schemas文件

接下来我们按照上述的步骤来实现自定义标签的过程

(1) 首先创建一个实体类

public class User {
private String name;
private  String email;

(2) 定义xsd组件

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns="http://www.jianshu.com/schema/user"
        targetNamespace="http://www.jianshu.com/schema/user"
        elementFormDefault="qualified">

<xsd:element name="user">
<xsd:complexType>
    <xsd:attribute name="userName" type="xsd:string" />
    <xsd:attribute name="email" type="xsd:string" />
</xsd:complexType>
</xsd:element>

</xsd:schema>

(3) 创建一个实现AbstractSingleBeanDefinitionParser的类

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

//Element所对应的class类
protected Class getBeanClass(Element element){

    return User.class;
}

//从element中解析并获取对应的元素
protected void doParse(Element element, BeanDefinitionBuilder bd){

    String name = element.getAttribute("name");
    String email = element.getAttribute("email");

    //将获取到的元素添加到BeanDefinitionBuilder中
    if (StringUtils.hasText(name)){

        bd.addPropertyValue("name",name);
    }
    if (StringUtils.hasText(email)){

        bd.addPropertyValue("email",email);
    }
}

}

(4) 创建一个继承于NamespaceHandlerSupport的Handler

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class UserNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
    registerBeanDefinitionParser("user",new UserBeanDefinitionParser());
}

(5) 对于 Spring.handler和Spring.schemas的编写,注意的是这两个文件一般默认放在resource/META-INT文件下,可以通过Spring扩展或修改源码的方法来改路径

  • Spring.handlers
http\://www.jianshu.com/schema/user=com.sgcc.bean.UserNamespaceHandler
  • Spring.schemas
http\://www.jianshu.com/schema/user.xsd=user.xsd
  • 编写配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:myTag="http://www.jianshu.com/schema/user"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans.xsd
   http://www.jianshu.com/schema/user http://www.jianshu.com/schema/user.xsd ">

<myTag:user id="user" email="12233445566@qq.com" userName="chenssy"/>

</beans>
  • 测试类
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
User user = (User) context.getBean("user");
System.out.println(user.getUserName() + "----" + user.getEmail());
  • 最后的运行结果如下:
微信截图_20190622175826.png

上面就是我们通过实现自定义标签简单的用法,我们在开发中常用的自定义的标签是<tx:annotation-driven>,接着我们来看解析的过程:

public BeanDefinition parseCustomElement(Element ele) {
    return parseCustomElement(ele, null);
}


/**
 *
 * @param ele
 * @param containingBd 父类的beanDefinition,如果是顶层父类beanDefinition将设置为null
 * @return
 */
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    //获取对应的命名空间
    String namespaceUri = getNamespaceURI(ele);
    if (namespaceUri == null) {
        return null;
    }
    //根据命名空间构建一个NamespaceHandler实例
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }
    //解析过程
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

上述就是解析自定义标签的过程,这里简单的总结下解析的每一步:

  • 首先是对命名空间的获取
BeanDefinitionParserDelegate.java
//获取命名空间的URL: http://www.springframework.org/schema/beans
@Nullable
public String getNamespaceURI(Node node) {
    return node.getNamespaceURI();
}
  • 通过我们拿到的命名空间来构建NamespaceHandler实例
XmlReaderContext.java
<1>.获取一个NamespaceHandlerResolver的解析器
/**
 * Return the namespace resolver.
 * @see XmlBeanDefinitionReader#setNamespaceHandlerResolver
 */
public final NamespaceHandlerResolver getNamespaceHandlerResolver() {
    return this.namespaceHandlerResolver;
}

DefaultNamespaceHandlerResolver.java
<2>.通过调用resolve方法构建一个NamespaceHandler实例
/**
 * Locate the {@link NamespaceHandler} for the supplied namespace URI
 * from the configured mappings.
 * @param namespaceUri the relevant namespace URI
 * @return the located {@link NamespaceHandler}, or {@code null} if none found
 */
@Override
@Nullable
public NamespaceHandler resolve(String namespaceUri) {
    //获取所有的已经配置了的handler映射器
    Map<String, Object> handlerMappings = getHandlerMappings();
    //根据我们的命名空间找到对应的处理器的名字
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        return null;
    }
    //如果是NamespaceHandler类型的
    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)) {
                throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                        "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
            }
            //实例化NamespaceHandler类
            NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            namespaceHandler.init();
            //保存到handlerMappings中
            handlerMappings.put(namespaceUri, namespaceHandler);
            return namespaceHandler;
        }
        catch (ClassNotFoundException ex) {
            throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
                    "] for namespace [" + namespaceUri + "]", ex);
        }
        catch (LinkageError err) {
            throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
                    className + "] for namespace [" + namespaceUri + "]", err);
        }
    }
}

上述构建NamespaceHandler的过程中,可以看到通过getHandlerMappings方法来读取Spring.handlers配置文件同时缓存到map集合中.

/** Stores the mappings from namespace URI to NamespaceHandler class name / instance. */
@Nullable
private volatile Map<String, Object> handlerMappings;


/**
 * Load the specified NamespaceHandler mappings lazily.
 */
private Map<String, Object> getHandlerMappings() {
    Map<String, Object> handlerMappings = this.handlerMappings;
    //如果为null,说明没有被缓存,那么可以缓存了
    if (handlerMappings == null) {
        //
        synchronized (this) {
            handlerMappings = this.handlerMappings;
            if (handlerMappings == null) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
                }
                try {
                    //加载handlerMappingsLocation
                    Properties mappings =
                            PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                    if (logger.isTraceEnabled()) {
                        logger.trace("Loaded NamespaceHandler mappings: " + mappings);
                    }
                    handlerMappings = new ConcurrentHashMap<>(mappings.size());
                    //将mappings合并到handlerMappings
                    CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                    this.handlerMappings = handlerMappings;
                }
                catch (IOException ex) {
                    throw new IllegalStateException(
                            "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
                }
            }
        }
    }
    return handlerMappings;
}
  • 解析过程
BeanDefinitionParserDelegate.java
handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));

ParserContext.java
<1>.首先是获取一个ParserContext实例
//BeanDefinition如果此时为顶级父类,默认为null
public ParserContext(XmlReaderContext readerContext, BeanDefinitionParserDelegate delegate,
        @Nullable BeanDefinition containingBeanDefinition) {

    this.readerContext = readerContext;
    this.delegate = delegate;
    this.containingBeanDefinition = containingBeanDefinition;
}
<2>.通过parse方法解析

/**
 * Parses the supplied {@link Element} by delegating to the {@link BeanDefinitionParser} that is
 * registered for that {@link Element}.
 */
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
    //通过element和parserContext来构建BeanDefinitionParser实例
    BeanDefinitionParser parser = findParserForElement(element, parserContext);
    //1.如果parser不为null,调用parse方法解析,并返回结果
    //为null时直接返回null
    return (parser != null ? parser.parse(element, parserContext) : null);
}

NamespaceHandlerSupport.java
<3>.通过调用findParserForElement方法获取BeanDefinitionParser解析器实例
//获取beanDefinitionParser解析器实例
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    String localName = parserContext.getDelegate().getLocalName(element);
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal(
                "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}
<4>.如果构建的BeanDefinitionParser解析器不为null,才是真正的解析过程

/** Constant for the "id" attribute. */
public static final String ID_ATTRIBUTE = "id";

/** Constant for the "name" attribute. */
public static final String NAME_ATTRIBUTE = "name";


@Override
@Nullable
public final BeanDefinition parse(Element element, ParserContext parserContext) {
    //获取一个beanDefinition实例
    AbstractBeanDefinition definition = parseInternal(element, parserContext);
    if (definition != null && !parserContext.isNested()) {
        try {
            //解析id属性
            String id = resolveId(element, definition, parserContext);
            if (!StringUtils.hasText(id)) {
                parserContext.getReaderContext().error(
                        "Id is required for element '" + parserContext.getDelegate().getLocalName(element)
                                + "' when used as a top-level tag", element);
            }
            String[] aliases = null;
            //
            if (shouldParseNameAsAliases()) {
                //获取name属性
                String name = element.getAttribute(NAME_ATTRIBUTE);

                if (StringUtils.hasLength(name)) {
                    aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
                }
            }
            //将AbstractBeanDefinition转化为BeanDefinitionHolder实例
            BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
            //注册
            registerBeanDefinition(holder, parserContext.getRegistry());
            //通知对应的事件监听器
            if (shouldFireEvents()) {
                BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
                postProcessComponentDefinition(componentDefinition);
                parserContext.registerComponent(componentDefinition);
            }
        }
        catch (BeanDefinitionStoreException ex) {
            String msg = ex.getMessage();
            parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
            return null;
        }
    }
    return definition;
}

在上述的解析过程中,主要做了对AbstractBeanDefinition的转化为beanDefinitionHolder和注册的过程,实际上spring将真正解析自定义标签的任务交给了AbstractSingleBeanDefinitionParser#parseInternal()方法,接下来我们看看该方法:

protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
    //构建BeanDefinitionBuilder实例
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
    //获取父类元素
    String parentName = getParentName(element);
    if (parentName != null) {
        builder.getRawBeanDefinition().setParentName(parentName);
    }
    //获取自定义标签的class
    //其次是会调用自定义解析器中的getBeanClass方法
    Class<?> beanClass = getBeanClass(element);
    if (beanClass != null) {
        builder.getRawBeanDefinition().setBeanClass(beanClass);
    }
    //beanClass为null
    else {
        //表明子类没有重写getBeanClass方法,则去尝试着检查是否重写getBeanClassName方法
        String beanClassName = getBeanClassName(element);
        if (beanClassName != null) {
            builder.getRawBeanDefinition().setBeanClassName(beanClassName);
        }
    }
    //封装属性
    builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
    BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
    if (containingBd != null) {
        //封装Scope属性
        // Inner bean definition must receive same scope as containing bean.
        builder.setScope(containingBd.getScope());
    }
    //如果是懒初始化
    if (parserContext.isDefaultLazyInit()) {
        // Default-lazy-init applies to custom bean definitions as well.
        //设置LazyInit属性
        builder.setLazyInit(true);
    }
    //调用子类的doParse进行解析
    doParse(element, parserContext, builder);
    return builder.getBeanDefinition();
}

简单的看一下parseInternal的解析过程:

  • 首先是构建一个BeanDefinitionBuilder构造器实例
/**
 * Create a new {@code BeanDefinitionBuilder} used to construct a {@link GenericBeanDefinition}.
 */
public static BeanDefinitionBuilder genericBeanDefinition() {
    return new BeanDefinitionBuilder(new GenericBeanDefinition());
}
  • 如果当前元素有parent元素,首先获取它的父元素
  protected String getParentName(Element element) {
    return null;
}

可以发现默认是没有实现的

  • 其次是获取自定义标签的class
protected Class<?> getBeanClass(Element element) {
    return null;

可以看到的是,没有实现默认允许让子类去实现它

  • 接着是对属性的封装
  • 调用子类的doParse方法进行解析
/**
 * Parse the supplied {@link Element} and populate the supplied
 * {@link BeanDefinitionBuilder} as required.
 * <p>The default implementation delegates to the {@code doParse}
 * version without ParserContext argument.
 * @param element the XML element being parsed
 * @param parserContext the object encapsulating the current state of the parsing process
 * @param builder used to define the {@code BeanDefinition}
 * @see #doParse(Element, BeanDefinitionBuilder)
 */
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
    doParse(element, builder);
}

/**
 * Parse the supplied {@link Element} and populate the supplied
 * {@link BeanDefinitionBuilder} as required.
 * <p>The default implementation does nothing.
 * @param element the XML element being parsed
 * @param builder used to define the {@code BeanDefinition}
 */
protected void doParse(Element element, BeanDefinitionBuilder builder) {
}

实际上该方法是可以允许子类去实现解析的逻辑,到这里关于自定义标签的解析全部说完,简单的来个小结

  • 首先是对自定义标签命名空间(namespaceUri)的获取
  • 其次是加载默认文件的source/META-INF下的spring.handlers并放入缓存中,以<namespaceUri,类路径>方式存放.
  • 接着通过获取到的namespaceUri通过反射的方式来获取NamespaceHandler实例
  • 然后调用NamespaceHandler#init方法初始化,实际上是注册对应的解析器和要获取的实例如:
public class UserNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
    registerBeanDefinitionParser("user",new UserBeanDefinitionParser());
}

上述代码是我们自己实现的过程,底层是将我们的user实体和自定义解析器进行了相关的注册操作.

  • 接着是调用NamespaceHandler#parse方法进行自定义标签的解析操作
  • 在解析前首先通过方法findParserForElement方法来进行解析器的获取操作
  • 其次是调用namespaceHandlerSupport#parser进行解析,主要是针对于AbstractBeanDefinition的转化和注册做了处理,然后是将真正的解析操作给了parseInternal方法来完成
  • 在parseInternal 方法中进行了一系列的属性设置操作如beanClass scope LazyInit等.
  • 最后调用子类的doParse方法进行解析的操作,我们也看到了是空实现,允许子类去做自己的解析逻辑处理

关于自定义标签的解析的过程这里就简单的说完了,接下来我们来了解spring是如何对beanDefinition的注册的过程......

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

推荐阅读更多精彩内容