Spring03-AOP

AOP 概述

全称是 Aspect Oriented Programming 底层实现是用的动态代理,在不改变源码的基础上对目标方法进行增强
作用: 在程序运行期间,不修改源码对已有方法进行增强。
优势: 减少重复代码 提高开发效率 维护方便。

动态代理

特点:字节码随用随创建,随用随加载。
作用:不修改源码的基础上对方法进行增强。
分类基于接口的动态代理,基于子类的动态代理

AOP 中的五种通知

前置通知:在目标方法之前执行
后置通知:在目标方法正常执行后,才会执行
异常通知:目标方法出异常了,执行
最终通知:无论目标方法是否正确执行,都会执行
环绕通知:就是以上4种的结合

AOP的相关术语(官方话术)

Joinpoint(连接点): 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的 连接点。
Pointcut(切入点): 被增强的点称为切入点。
Advice(通知/增强): 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。

Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方 法或 Field。
Target(目标对象): 代理的目标对象。
Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy(代理): 一个类被 AOP 织入增强后,就产生一个结果代理类。
**Aspect(切面): ** 是切入点和通知(引介)的结合。


AOP的相关术语(说人话)

[1] 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。

----类中可以被增强的方法 ==> 就是连接点(Joinpoint)

[2] 切点(pointcut):每个类都拥有多个连接点:例如 类中所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。(你想切入到指定目标类的哪些方法上面)
譬如: 你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中哪几个你想要的方法。

----类中真正被增强的方法 ==> 就是切点
----譬如:一个类有增删改查四个方法,都可以被增强,但是实际上只增强了增跟删方法,那么增跟删就被称为切点

[3] 通知(Advice): (又称增强)切面必须要完成的工作 (需求:日志,事务,验证等)

----譬如现在要给方法增加日志功能,那么这个功能就被称为叫 通知/增强

[4] 切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象 (通知和切入点的结合)

----通知/增强 + 切点 = 切面

[5] 目标(Target): 被通知的对象 (被追踪的目标类,也就是真正的业务逻辑)

----要增强的类

[6] 织入
--- 把 增强 应用到 目标的过程

[7] 代理(Proxy): 向目标对象应用通知之后创建的对象

----一个类被织入增强后,产生的结果就是一个代理(代理类)


动态代理

基于接口的动态代理:
提供者:JDK 官方的 Proxy 类。 要求:被代理类最少实现一个接口。 基于子类的动态代理

基于子类的动态代理
提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。 要求:被代理类不能用 final 修饰的类(最终类)。

使用 JDK 官方的 Proxy 类创建代理对象

此处我们使用的是一个演员的例子: 在很久以前,演员和剧组都是直接见面联系的。没有中间人环节。 而随着时间的推移,产生了一个新兴职业:经纪人(中间人),这个时候剧组再想找演员就需要通过经纪 人来找了。下面我们就用代码演示出来。

/** 
 * 一个经纪公司的要求: 
 *   能做基本的表演和危险的表演
 */ 
public interface IActor {
    /** 
      * 基本演出
     * @param money
     */ 
       public void basicAct(float money);
  /** 
    * 危险演出
    * @param money
    */ 
       public void dangerAct(float money);
 } 
/** 
 * 一个演员 
 */ 
//实现了接口,就表示具有接口中的方法实现。即:符合经纪公司的要求
 public class Actor implements IActor{
    public void basicAct(float money){ 
      System.out.println("拿到钱,开始基本的表演:"+money);
    }
    public void dangerAct(float money){
      System.out.println("拿到钱,开始危险的表演:"+money);
   }
 } 

public class Client {
    public static void main(String[] args) { 
  //一个剧组找演员:
     final Actor actor = new Actor();//直接
      /** 
         * 代理:
        *  间接。
        * 获取代理对象:
        *  要求:
        *   被代理类最少实现一个接口
        * 创建的方式
        *   Proxy.newProxyInstance(三个参数)
        * 参数含义:
        *  ClassLoader:和被代理对象使用相同的类加载器。
        *  Interfaces:和被代理对象具有相同的行为。实现相同的接口。
        *  InvocationHandler:提供增强的代码,一般写的都是该接口的实现类,通常情况下都是
        *  匿名内部类,但不是必须的,此接口的实现类就是谁用谁写。    
        *    策略模式:使用场景是: 
        *       数据有了,目的明确。
        *       如何达成目标,就是策略。
        * 
        */
       IActor proxyActor = (IActor) Proxy.newProxyInstance(
           actor.getClass().getClassLoader(),
            actor.getClass().getInterfaces(), 
           new InvocationHandler() { 
    /** 
      * 执行被代理对象的任何方法,都会经过该方法。
      * 此方法有拦截的功能。
      *  
      * 参数:
      *  proxy:代理对象的引用。不一定每次都用得到      
      *  method:当前执行的方法对象   
      *  args:执行方法所需的参数
      * 返回值:
      *  当前执行方法的返回值
      */
     @Override
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {      
              String name = method.getName(); 
               Float money = (Float) args[0];
              Object rtValue = null;
//每个经纪公司对不同演出收费不一样,此处开始判断
      if("basicAct".equals(name)){
       //基本演出,没有 2000 不演
         if(money > 2000){
          //看上去剧组是给了 8000,实际到演员手里只有 4000
          //这就是我们没有修改原来 basicAct 方法源码,对方法进行了增强
          rtValue = method.invoke(actor, money/2);
       } 
     }      if("dangerAct".equals(name)){ 
        //危险演出,没有 5000 不演
             if(money > 5000){ 
       //看上去剧组是给了 50000,实际到演员手里只有 25000
        //这就是我们没有修改原来 dangerAct 方法源码,对方法进行了增强 
        rtValue = method.invoke(actor, money/2); 
        }
      }
      return rtValue;
     } 
  }); 
  //没有经纪公司的时候,直接找演员。
   //  actor.basicAct(1000f);
   //  actor.dangerAct(5000f);    

  //剧组无法直接联系演员,而是由经纪公司找的演员
   proxyActor.basicAct(8000f); 
  proxyActor.dangerAct(50000f);  } 
} 

使用 CGLib 的 Enhancer 类创建代理对象

还是那个演员的例子,只不过不让他实现接口。
 /** 
 * 一个演员 
*/ 
public class Actor{
//没有实现任何接口
    public void basicAct(float money){ 
          System.out.println("拿到钱,开始基本的表演:"+money);  } 
   public void dangerAct(float money){ 
          System.out.println("拿到钱,开始危险的表演:"+money); 
    } 
}
public class Client {
  /** 
  * 基于子类的动态代理 
  *  要求:
   *     被代理对象不能是最终类 
  *  用到的类:
   *     Enhancer 
  *  用到的方法:
   *     create(Class, Callback) 
  *  方法的参数:
   *     Class:被代理对象的字节码 
  *      Callback:如何代理
   * @param args
   */  
public static void  main(String[] args) {
   final Actor actor = new Actor();
      Actor cglibActor = (Actor) Enhancer.create(actor.getClass(), 
       new MethodInterceptor() {
    /** 
    * 执行被代理对象的任何方法,都会经过该方法。在此方法内部就可以对被代理对象的任何 方法进行增强。
     *  
    * 参数:
    *  前三个和基于接口的动态代理是一样的。
    *  MethodProxy:当前执行方法的代理对象。
     * 返回值:
     *    当前执行方法的返回值
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { 
    String name = method.getName();
     Float money = (Float) args[0]; 
    Object rtValue = null; 
    if("basicAct".equals(name)){ 
     //基本演出
      if(money > 2000){
       rtValue = method.invoke(actor, money/2); 
         }
     } 
  if("dangerAct".equals(name)){ 
     //危险演出 
     if(money > 5000){ 
      rtValue = method.invoke(actor, money/2); 
     } 
    }
     return rtValue;
    } 
  });
     cglibActor.basicAct(10000); 
    cglibActor.dangerAct(100000);
  } 
}

3 基于xml的 AOP 配置

1 搭建环境
2 导入Maven坐标
3 创建spring的配置文件并导入约束 src---main---resources---bean.xml

<?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:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans.xsd 
             http://www.springframework.org/schema/aop
               http://www.springframework.org/schema/aop/spring-aop.xsd"> 
</beans> 

4 配置spring的ioc

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置srping的Ioc,把service对象配置进来-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>

    <!--spring中基于XML的AOP配置步骤
        1、把通知Bean也交给spring来管理
        2、使用aop:config标签表明开始AOP的配置
        3、使用aop:aspect标签表明配置切面
                id属性:是给切面提供一个唯一标识
                ref属性:是指定通知类bean的Id。
        4、在aop:aspect标签的内部使用对应标签来配置通知的类型
               我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知
               aop:before:表示配置前置通知
                    method属性:用于指定Logger类中哪个方法是前置通知
                    pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强

           
    -->

    <!-- 配置Logger类 -->
    <bean id="logger" class="com.itheima.utils.Logger"></bean>

    <!--配置AOP,使用 aop:config 声明 aop 配置 -->
    <aop:config>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
            <aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
        </aop:aspect>
    </aop:config>

</beans>

配置步骤
第一步:把通知类用 bean 标签配置起来

<!-- 配置通知 --> 
  <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>

第二步:使用 aop:config 声明 aop 配置

aop:config: 
 作用:用于声明开始 aop 的配置
    <aop:config> 
    <!-- 配置的代码都写在此处 --> 
   </aop:config> 

第三步:使用 aop:aspect 配置切面 使用 aop:before 配置切入点表达式

aop:aspect: 
  作用:   用于配置切面。
  属性:   id:给切面提供一个唯一标识。  
               ref:引用配置好的通知类 bean 的 id。
 <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
            <aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
        </aop:aspect

使用 aop:xxx 配置对应的通知类型

aop:before 
 作用:   用于配置前置通知。指定增强的方法在切入点方法之前执行   
  属性:   method:用于指定通知类中的增强方法名称
                 ponitcut-ref:用于指定切入点的表达式的引用   
                poinitcut:用于指定切入点表达式
   执行时间点:   切入点方法执行之前执行
 <aop:before method="beginTransaction" pointcut-ref="pt1"/> 
 
aop:after-returning 
作用:   用于配置后置通知  
属性:   method:指定通知中方法的名称。
             pointct:定义切入点表达式   
            pointcut-ref:指定切入点表达式的引用  执行时间点:   切入点方法正常执行之后。它和异常通知只能有一个执行
         <aop:after-returning method="commit" pointcut-ref="pt1"/> 
 
aop:after-throwing 
 作用:   用于配置异常通知
 属性:   method:指定通知中方法的名称。
               pointct:定义切入点表达式   
               pointcut-ref:指定切入点表达式的引用  执行时间点:   切入点方法执行产生异常后执行。它和后置通知只能执行一个
             <aop:after-throwing method="rollback" pointcut-ref="pt1"/>
 aop:after 
 作用:   用于配置最终通知  
属性:   method:指定通知中方法的名称。
             pointct:定义切入点表达式  
             pointcut-ref:指定切入点表达式的引用  执行时间点:   无论切入点方法执行时是否有异常,它都会在其后面执行。
 <aop:after method="release" pointcut-ref="pt1"/> 

切入点表达式说明

 切入点表达式的写法:
                关键字:execution(表达式)
                表达式:
                    访问修饰符  返回值  包名.包名.包名...类名.方法名(参数列表)
                标准的表达式写法:
                    public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
                访问修饰符可以省略
                    void com.itheima.service.impl.AccountServiceImpl.saveAccount()
                返回值可以使用通配符,表示任意返回值
                    * com.itheima.service.impl.AccountServiceImpl.saveAccount()
                包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
                    * *.*.*.*.AccountServiceImpl.saveAccount())
                包名可以使用..表示当前包及其子包
                    * *..AccountServiceImpl.saveAccount()
                类名和方法名都可以使用*来实现通配
                    * *..*.*()
                参数列表:
                    可以直接写数据类型:
                        基本类型直接写名称           int
                        引用类型写包名.类名的方式   java.lang.String
                    可以使用通配符表示任意类型,但是必须有参数
                    可以使用..表示有无参数均可,有参数可以是任意类型
                全通配写法:
                    * *..*.*(..)

                实际开发中切入点表达式的通常写法:
                    切到业务层实现类下的所有方法
                        * com.itheima.service.impl.*.*(..)

四种通知类型:

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置srping的Ioc,把service对象配置进来-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>


    <!-- 配置Logger类 -->
    <bean id="logger" class="com.itheima.utils.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
        <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
              此标签写在aop:aspect标签内部只能当前切面使用。
              它还可以写在aop:aspect外面,此时就变成了所有切面可用
          -->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置前置通知:在切入点方法执行之前执行-->
            <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>

            <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个-->
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>

            <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个-->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>

            <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行-->
            <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>

            <!-- 配置环绕通知 详细的注释请看Logger类中-->
            <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>

</beans>

实例代码:

package com.itheima.service.impl;

import com.itheima.service.IAccountService;

/**
 * 账户的业务层实现类
 */
public class AccountServiceImpl implements IAccountService{

    @Override
    public void saveAccount() {
        System.out.println("执行了保存");
    }

    @Override
    public void updateAccount(int i) {
        System.out.println("执行了更新"+i);

    }

    @Override
    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}
package com.itheima.service;

/**
 * 账户的业务层接口
 */
public interface IAccountService {

    /**
     * 模拟保存账户
     */
   void saveAccount();

    /**
     * 模拟更新账户
     * @param i
     */
   void updateAccount(int i);

    /**
     * 删除账户
     * @return
     */
   int  deleteAccount();
}
package com.itheima.utils;

/**
 * 用于记录日志的工具类,它里面提供了公共的代码
 */
public class Logger {

    /**
     * 用于打印日志:计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
     */
    public  void printLog(){
        System.out.println("Logger类中的pringLog方法开始记录日志了。。。");
    }
}
package com.itheima.test;

import com.itheima.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 测试AOP的配置
 */
public class AOPTest {

    public static void main(String[] args) {
        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        IAccountService as = (IAccountService)ac.getBean("accountService");
        //3.执行方法
        as.saveAccount();
        as.updateAccount(1);
        as.deleteAccount();
    }
}

3 基于注解的 AOP 配置实例代码

src\main\resources

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>

    <!-- 配置spring开启注解AOP的支持 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

src\main\java\com\itheima\service\impl

package com.itheima.service.impl;

import com.itheima.service.IAccountService;
import org.springframework.stereotype.Service;

/**
 * 账户的业务层实现类
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService{

    @Override
    public void saveAccount() {
        System.out.println("执行了保存");
        int i=1/0;
    }

    @Override
    public void updateAccount(int i) {
        System.out.println("执行了更新"+i);

    }

    @Override
    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}

src\main\java\com\itheima\service

package com.itheima.service;

/**
 * 账户的业务层接口
 */
public interface IAccountService {

    /**
     * 模拟保存账户
     */
   void saveAccount();

    /**
     * 模拟更新账户
     * @param i
     */
   void updateAccount(int i);

    /**
     * 删除账户
     * @return
     */
   int  deleteAccount();
}

src\main\java\com\itheima\utils

package com.itheima.utils;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 用于记录日志的工具类,它里面提供了公共的代码
 */
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){}

    /**
     * 前置通知
     */
//    @Before("pt1()")
    public  void beforePrintLog(){
        System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
    }

    /**
     * 后置通知
     */
//    @AfterReturning("pt1()")
    public  void afterReturningPrintLog(){
        System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
    }
    /**
     * 异常通知
     */
//    @AfterThrowing("pt1()")
    public  void afterThrowingPrintLog(){
        System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
    }

    /**
     * 最终通知
     */
//    @After("pt1()")
    public  void afterPrintLog(){
        System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
    }

    /**
     * 环绕通知
     * 问题:
     *      当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
     * 分析:
     *      通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
     * 解决:
     *      Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
     *      该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
     *
     * spring中的环绕通知:
     *      它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
     */
    @Around("pt1()")
    public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//得到方法执行所需的参数

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");

            rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

            return rtValue;
        }catch (Throwable t){
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
        }
    }
}

src\test\java\com\itheima\test

package com.itheima.test;

import com.itheima.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 测试AOP的配置
 */
public class AOPTest {

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

推荐阅读更多精彩内容