细呀,IDEA还可以这样搭建半自动映射框架Mybatis

首先我们建立一个SpringBoot工程,然后导入mybatis-spring-boot-starter依赖

<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency>

导入后发现这个依赖其实就是帮助我们导入了mybatis需要的依赖,其中和自动配置相关最重要的一个就是mybatis-spring-boot-autoconfigure

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> </dependency> </dependencies>

MyBatis自动配置中是如何工作的

如上面分析自动配置的关键类我们就从mybatis-spring-boot-autoconfigure开始着手分析。

spring.factories

# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

从spring.factories文件看到这里通过SPI机制加载了两个类

MybatisAutoConfiguration

MybatisLanguateDriverAutoConfiguration.

MybatisAutoConfiguration

//表示这是一个Spring配置类 @Configuration  //这个类需要在classpath中存在SqlSessionFactory和SqlSessionFactoryBean时才生效 @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) -- //这个类需要有一个DataSource的Canidate注册到Spring容器中  @ConditionalOnSingleCandidate(DataSource.class) //使MybatisProperties注解类生效 @EnableConfigurationProperties({MybatisProperties.class}) //需要在DataSourceAutoConfiguration和MybatisLanguageDriverAutoConfiguration自动配置之后执行 @AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class}) public class MybatisAutoConfiguration implements InitializingBean { }

MybatisAutoConfiguration#sqlSessionFactory

@Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { //创建一个SqlSessionFactoryBean, 在mybatis-spring项目下    SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) {     factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } //应用Configuration对象 this.applyConfiguration(factory); if (this.properties.getConfigurationProperties() != null) { factory.setConfigurationProperties(this.properties.getConfigurationProperties()); } if (!ObjectUtils.isEmpty(this.interceptors)) { factory.setPlugins(this.interceptors); } if (this.databaseIdProvider != null) { factory.setDatabaseIdProvider(this.databaseIdProvider); } if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); } if (this.properties.getTypeAliasesSuperType() != null) { factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType()); } if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); } if (!ObjectUtils.isEmpty(this.typeHandlers)) { factory.setTypeHandlers(this.typeHandlers); } if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { factory.setMapperLocations(this.properties.resolveMapperLocations()); }  Set<String> factoryPropertyNames = (Set)Stream.of((new BeanWrapperImpl(SqlSessionFactoryBean.class)).getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet()); Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver(); if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) { factory.setScriptingLanguageDrivers(this.languageDrivers); if (defaultLanguageDriver == null && this.languageDrivers.length == 1) { defaultLanguageDriver = this.languageDrivers[0].getClass(); } } //设置默认的语言驱动类 if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) { factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver); } //这里默认会返回一个DefaultSqlSessionFactory对象 return factory.getObject(); }

MybatisAutoConfiguration#sqlSessionTemplate

@Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { ExecutorType executorType = this.properties.getExecutorType(); return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory); }

这里也就知道了MyBatis自动配置其实就是替我们完成了SqlSessionFactory和SqlSessionTempate的创建, 省去了自己导入相关依赖和配置相关Bean的麻烦.

MybatisLanguageDriverAutoConfiguration

这个类的配置是对各个语言的支持,比如Thymeleaf, Velocity,LegacyVelociy, FreeMarker等视图组件的支持。

@Configuration @ConditionalOnClass({LanguageDriver.class}) public class MybatisLanguageDriverAutoConfiguration {    private static final String CONFIGURATION_PROPERTY_PREFIX = "mybatis.scripting-language-driver";      public MybatisLanguageDriverAutoConfiguration() { }      @Configuration    @ConditionalOnClass({ThymeleafLanguageDriver.class})    public static class ThymeleafConfiguration {        public ThymeleafConfiguration() { } }      @Configuration    @ConditionalOnClass({VelocityLanguageDriver.class, VelocityLanguageDriverConfig.class})    public static class VelocityConfiguration {        public VelocityConfiguration() { } }      @Configuration    @ConditionalOnClass({Driver.class})          @ConditionalOnMissingClass({"org.mybatis.scripting.velocity.VelocityLanguageDriverConfig"})    public static class LegacyVelocityConfiguration {        public LegacyVelocityConfiguration() { } }      @Configuration    @ConditionalOnClass({FreeMarkerLanguageDriver.class, FreeMarkerLanguageDriverConfig.class})    public static class FreeMarkerConfiguration {        public FreeMarkerConfiguration() { } }      @Configuration    @ConditionalOnClass({FreeMarkerLanguageDriver.class})    @ConditionalOnMissingClass({"org.mybatis.scripting.freemarker.FreeMarkerLanguageDriverConfig"})    public static class LegacyFreeMarkerConfiguration {        public LegacyFreeMarkerConfiguration() { } } }

MybatisLanguageDriverAutoConfiguration类在org.mybatis.spring.boot.autoconfigure包下,我删掉了内部静态类下的代码,为了保持这个类看起来更直观

自定义Mapper是如何被扫描的

业务开发中,我们是声明接口(Mapper),那么我们自定义的Mapper是如何被扫描的呢, 我们继续顺着MybatisAutoConfiguration代码分析,其内部包含了一个AutoConfiguredMapperScannerRegistrar的内部静态类.

AutoConfiguredMapperScannerRegistrar

registerBeanDefinitions

public static class AutoConfiguredMapperScannerRegistrar      implements BeanFactoryAware, ImportBeanDefinitionRegistrar { private BeanFactory beanFactory;  public AutoConfiguredMapperScannerRegistrar() { }  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (!AutoConfigurationPackages.has(this.beanFactory)) { } else { //1.获取到SpringBoot的基础包路径 List<String> packages = AutoConfigurationPackages.get(this.beanFactory); //2.生成一个BeanDefinition的构造器,用于构建MapperScannerConfigurer的                        //BeanDefinition BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); builder.addPropertyValue("processPropertyPlaceHolders", true); //3.设置@Mapper注解的接口才会被当成Mapper接口 builder.addPropertyValue("annotationClass", Mapper.class);              builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages)); BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class); //4.获取MapperScannerConfigurer的属性名称            Set<String> propertyNames = (Set)Stream.of(beanWrapper.getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet()); if (propertyNames.contains("lazyInitialization")) { builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"); } if (propertyNames.contains("defaultScope")) { builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}"); } //5.这里添加一个MapperScannerConfigurer的BeanDefinition对象,也就是注入一个 //MapperScannerConfigurer对象 registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); } }  public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } }

AutoConfiguredMapperScannerRegistrar类是MybatisAutoConfiguration的内部静态类,位于包org.mybatis.spring.boot.autoconfigure下。

可以看到这个类实现了ImportBeanDefinitionRegistrar接口,ImportBeanDefinitionRegistrar接口是Spring用来动态注册Bean的,也就是会向Spring容器中注入一个BeanDefinition, 这个BeanDefinition就是MapperScannerConfigurer。

ImportBeanDefinitionRegistrar实现类只能通过其他类@Import的方式来加载,通常是配置类或者启动类,所以MybatisAutoConfiguration类下还有一个内部类MapperScannerRegistrarNotFoundConfiguration如下。

@Configuration @Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class}) @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class}) public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean { }

这个方法里最后会调用接口BeanDefinitionRegistry.registerBeanDefinition, beanName是"org.mybatis.spring.mapper.MapperScannerConfigurer", registerBeanDefinition方法实际会调用DefaultListableBeanFactory.registerBeanDefinition。DefaultListableBeanFactory是BeanDefinitionRegistry接口的实现类。

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory      implements ConfigurableListableBeanFactory,                BeanDefinitionRegistry,                Serializable { }

AutoConfiguredMapperScannerRegistrar类和MapperScanner注解的作用是一样的,如果你没有通过以下三种配置方式扫描Mapper接口的包路径

配置MapperScannerConfigurer扫描器类型的Spring Bean

@Mapper注解

<mybatis: scan/>标签

那么这里就会通过AutoConfiguredMapperScannerRegistrar类添加一个MapperScannerConfigurer扫描器对象,去扫描SpringBoot包设置的基础包路径,也就是启动类的同级目录。 如果设置了@Mapper注解,则会当成Mapper接口解析,那么这里自动配置则不生效。

MapperScannerConfigurer

MapperScannerConfigurer

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,BeanDefinitionRegistryPostProcessor 接口又继承了BeanFactoryPostProcessor接口, 也就是说在MapperScannerConfigurer类里需要实现这两个接口的方法。

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor { void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException; }  @FunctionalInterface public interface BeanFactoryPostProcessor { void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException; }

在MapperScannerConfigurer类里可以看到这里只实现了postProcessBeanDefinitionRegistry。

BeanDefinitionRegistryPostProcessor

Spring里有两个用来动态注册Bean到容器中(BeanDefinitionRegistryPostProcessor和ImportBeanDefinitionRegistrar)。ImportBeanDefinitionRegistrar上文中有提到。

BeanDefinitionRegistryPostProcessor接口实现了BeanFactoryPostProcessor接口,是Spring框架的BeanDefinitionRegistry的后置处理器,用来注册额外的BeanDefinition,postProcessBeanDefinitionRegistry方法会在所有的beanDefinition被加载了,但是所有的Bean还没创建前调用。BeanDefinitionRegistryPostProcessor经常被用来注册BeanFactoryPostProcessor的BeanDefinition。

postProcessBeanDefinitionRegistry

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {          public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) {            this.processPropertyPlaceHolders(); }          ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);        scanner.setAddToConfig(this.addToConfig);        scanner.setAnnotationClass(this.annotationClass);        scanner.setMarkerInterface(this.markerInterface);        scanner.setSqlSessionFactory(this.sqlSessionFactory);        scanner.setSqlSessionTemplate(this.sqlSessionTemplate);        scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);        scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);        scanner.setResourceLoader(this.applicationContext);        scanner.setBeanNameGenerator(this.nameGenerator);        scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass); if (StringUtils.hasText(this.lazyInitialization)) {            scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization)); } if (StringUtils.hasText(this.defaultScope)) {            scanner.setDefaultScope(this.defaultScope); }          scanner.registerFilters();        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n")); } }

MapperScannerConfigurer在包org.mybatis.spring.mapper下.

这里会调用ClassPathMapperScanner.scan(),而ClassPathMapperScanner又继承了ClassPathBeanDefinitionScanner,所以这里scan()会调用ClassPathBeanDefinitionScanner.scan(), 而ClassPathBeanDefinitionScanner.scan() 第二句代码又调用了this.doScan(basePackages), this.doScan()又调用了ClassPathMapperScanner.doScan(), 而这个方法第一句代码又调用了super.doScan(basePackages),父子类来回互相调用,有点晕头转向的。

org.mybatis.spring.mapper.ClassPathMapperScanner

org.springframework.context.annotation.ClassPathBeanDefinitionScanner这个类在spring-context.jar

ClassPathMapperScanner

ClassPathBeanDefinitionScanner#scan

public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount();    this.doScan(basePackages); if (this.includeAnnotationConfig) {        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } return this.registry.getBeanDefinitionCount() - beanCountAtScanStart; }

ClassPathMapperScanner#doScan()

这个方法里在mybatis自动配置算比较重要的一个方法,也就是帮助我们自动配置MapperFactoryBean, 会把根据basePackage注册进Spring容器的BeanDefinition的beanClass设置成MapperFactoryBean。

public Set<BeanDefinitionHolder> doScan(String... basePackages) {    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) {        LOGGER.warn(() -> { return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."; }); } else { //这是一个关键方法,会处理已经注册进Spring容器的beanDefinition,也就是会把 //已经注册进Spring容器的beanDefinitiond的beanClass为MapperFactoryBean        this.processBeanDefinitions(beanDefinitions); } return beanDefinitions; }

ClassPathBeanDefinitionScanner#doScan()

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {    Assert.notEmpty(basePackages, "At least one base package must be specified");    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet();    String[] var3 = basePackages; int var4 = basePackages.length; for(int var5 = 0; var5 < var4; ++var5) {        String basePackage = var3[var5]; //这个方法会扫描指定basePackage下被@Mapper注解标注的接口        Set<BeanDefinition> candidates = this.findCandidateComponents(basePackage);        Iterator var8 = candidates.iterator(); while(var8.hasNext()) {            BeanDefinition candidate = (BeanDefinition)var8.next();            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);            candidate.setScope(scopeMetadata.getScopeName()); //这里获取beanName, 默认值是类名首字母小写            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) {                this.postProcessBeanDefinition((AbstractBeanDefinition)candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) {                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition)candidate); } //检查对应的Mapper接口是否被注册进Spring容器中。 if (this.checkCandidate(beanName, candidate)) {                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);                beanDefinitions.add(definitionHolder);                this.registerBeanDefinition(definitionHolder, this.registry); } } } //这个集合返回以后 Spring容器会将里面的所有内容注册到容器中 return beanDefinitions; }

ClassPathMapperScanner#processBeanDefinitions

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { BeanDefinitionRegistry registry = this.getRegistry(); Iterator var4 = beanDefinitions.iterator(); while(var4.hasNext()) { BeanDefinitionHolder holder = (BeanDefinitionHolder)var4.next(); AbstractBeanDefinition definition = (AbstractBeanDefinition)holder.getBeanDefinition(); boolean scopedProxy = false; if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) { definition = (AbstractBeanDefinition)Optional.ofNullable(((RootBeanDefinition)definition).getDecoratedDefinition()).map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> { return new IllegalStateException("The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"); }); scopedProxy = true; }  String beanClassName = definition.getBeanClassName(); LOGGER.debug(() -> { return "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface"; }); definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); //1.这里把普通接口设置成MapperFactoryBean definition.setBeanClass(this.mapperFactoryBeanClass); //2.是否把Mapper接口加入到Mybatis的Config当中去, 这里设置为true definition.getPropertyValues().add("addToConfig", this.addToConfig); definition.setAttribute("factoryBeanObjectType", beanClassName); boolean explicitFactoryUsed = false; //2.从核心容器里获取SqlSessionFactory赋值给MapperFactoryBean if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionFactory != null) { definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory); explicitFactoryUsed = true; } //3.从核心容器里获取SqlSessionTemplate赋值给MapperFactoryBean if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { if (explicitFactoryUsed) { LOGGER.warn(() -> { return "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."; }); }  definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionTemplate != null) { if (explicitFactoryUsed) { LOGGER.warn(() -> { return "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."; }); }  definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; } if (!explicitFactoryUsed) { LOGGER.debug(() -> { return "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."; }); definition.setAutowireMode(2); }  definition.setLazyInit(this.lazyInitialization); if (!scopedProxy) { if ("singleton".equals(definition.getScope()) && this.defaultScope != null) { definition.setScope(this.defaultScope); } if (!definition.isSingleton()) { BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true); if (registry.containsBeanDefinition(proxyHolder.getBeanName())) { registry.removeBeanDefinition(proxyHolder.getBeanName()); }  registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition()); } } } }

上面只是完成了MyBatis 自动配置的工作,那么这些自动配置是如何在SpringBoot启动时执行呢? 还记得SpringBoot的EnableAutoConfiguration注解吧,它是利用SpringFactoriesLoader机制加载所有的AutoConfiguration类,所以我们需要把编写好的自动配置类放在META-INF/spring.factories文件中,这也就是我们一开始分析的mybatis-spring-boot-starter-autoconfigure的文件结构的原因。

# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

总结

mybatis-spring-boot-starter将mybatis需要的依赖全部引入。

starter通过SPI机制引入了一个配置的Class(MyBatisAutoConfiguration)。它负责注册SqlSessionFactory和SqlSessionTemplate到Spring容器中,使用MyBatis开发时绝大部分功能要使用这两个类来完成。

注入了AutoConfiguredMapperScannerRegistrar这个Bean到Spring容器,它负责将MapperScanner引入到Spring容器,然后MapperScanner会将工程中指定package下的Mapper转化为BeanDefinition并且注册到Spring容器中。

在开发中使用某个具体的Mapper时,Spring能够从容器中找到这个Mapper对应的BeanDefinition,将其实例化并且注入,这样开发者就可以使用了。

SpringBoot+MyBatis自动配置涉及到Spring中两个自动注册Bean的关键接口(BeanDefinitionRegistryPostProcessor和ImportBeanDefinitionRegistrar),也是我们业务开发中需要重点关注的地方。

还有不懂的小伙伴可以添加作者的联系方式

QQ:1162798594

vx:tan1999nn

喜欢的小伙伴可以给作者我点点赞,点点关注哈~~~

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

推荐阅读更多精彩内容