AOP

AOP 基本概念

OOP是自上而下从controller-service-dao-数据库。

AOP(Aspect-Oriented Programming)面向切面编程。AOP工作原理一句话概括:通过代理模式为目标对象生产代理对象,并将横切逻辑插入到目标方法执行的前后。

术语

切面(Aspect)

切面是切点和通知的集合,一般单独作为一个类。通知和切点共同定义了关于切面的全部内容。

白话文理解:影响了多个类的公共行为封装到一个可重用模块。

切点(PointCut)

切点是对一系列代表同种功能(目的)的切入点(连接点)的统称,切点不是一个点,而是代表某一功能的一系列连接点的集合。通常使用明确的类或者方法名称,或是利用正则表达式定义所匹配的类和方法来指定这些切点。

通知(Advice)

通知就是我们要在切点执行的操作,就是我们要实现的目的,是要实现的功能的代码实现。一般通知又称为增强。

通知有物种类型:

  • Before 在方法被调用之前调用

  • After 在方法完成后调用通知,无论方法是否执行成功

  • After-returning 在方法成功执行之后调用通知

  • After-throwing 在方法抛出异常后调用通知

  • Around 通知了好、包含了被通知的方法,在被通知的方法调用之前后调用之后执行自定义的行为

这里需要注意Around不是执行两次。如下代码,而是在pjp.proceed()方法前后的环绕。


@Around("controllerAspect()")

    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        StopWatch stopWatch = new StopWatch();

        Object result = null;

        Transaction transaction = null;

        String uri = "";

        try {

            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

            ServletContext ctx = request.getSession().getServletContext();

            uri = request.getRequestURI().replace(ctx.getContextPath(), "").toLowerCase();

            uri = uri.substring(uri.lastIndexOf("/") + 1);

            if (uri.trim().matches("^\\d+$")) {

                String numberUri = uri.trim();

                uri = request.getRequestURI().replace(ctx.getContextPath(), "")

                        .replace("/", "").replace(numberUri, "")

                        .toLowerCase();

            }

            logger.info(String.format("controller path uri:%s", uri));

            transaction = Cat.newTransaction(TYPE, uri);

            Cat.logMetric(uri);

            // 执行方法

            result = pjp.proceed();

            transaction.setStatus(Transaction.SUCCESS);

            // 默认执行完方法不报错就是SUCCESS

            Cat.logEvent(TYPE, uri, Event.SUCCESS, "");

        } catch (Throwable e) {

            logger.error("方法执行失败 controller path=" + uri, e);

            transaction.setStatus(e);

            // 默认执行完报错就记失败

            Cat.logEvent(TYPE, uri, e.getMessage(), "");

            throw e;

        } finally {

            transaction.complete();

        }

        logger.info("controller path={} spent {} seconds", uri, stopWatch.elapsedTime());

        return result;

    }

连接点(JoinPoint)

连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时,抛出异常时,甚至修改一个字段时。切面代码可以利用这些点插入应用的正常流程中,并添加新的行为。Spring只支持方法的连接点

织入(Weaving)

织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入。

Spring AOP的切面织入是在运行时被织入,原理是使用了动态代理技术,Spring支持两种方式生产代理对象:JDK动态代理和CGLIB,默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理

引入(Introduction)

添加方法或字段到被通知的类。Spring允许引入新的接口到任何被通知的对象。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口。

目标对象(Target Object)

包含连接点的对象。也被称作被通知或被代理对象。

AOP代理(AOP Proxy)

AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

源码分析

**1、AOPNamespaceHandler中init是在什么时候调用的

2、对于XML配置AOP加载过程已了解,那么对于SpringBoot配置AOP加载过程呢。**

**XML配置AOP加载过程。

3、AOP有几种配置方式,每种配置方式,源码都是怎么走向。**

1、xml配置 <aop:config>标签使用分析

refresh

-> obtainFreshBeanFactory

-> refreshBeanFactory

-> AbstractRefreshableApplicationContext.loadBeanDefinitions(beanFactory)

-> ...

-> XmlBeanDefinitionReader.loadBeanDefinitions(Resource resource)

-> DefaultBeanDefinitionDocumentReader.parseBeanDefinitions

对于<aop:config>是自定义的标签不是defaultNamespace(默认是bean标签),那么走的是parseCustomElement方法


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.parseCustomElement(ele);

}

}

}

}

else {

delegate.parseCustomElement(root);

}

}


public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {

String namespaceUri = getNamespaceURI(ele);

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));

}

那么这个Handler就对应META/spring.handlers文件中。Spring各个jar包中的spring.handlers都会生效。

image.png

我们在看下NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);中的resolve方法,此方法拿到namespaceHandler之后会调用init方法。


@Override

public NamespaceHandler resolve(String namespaceUri) {

Map<String, Object> handlerMappings = getHandlerMappings();

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)) {

throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +

"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");

}

NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);

namespaceHandler.init();

handlerMappings.put(namespaceUri, namespaceHandler);

return namespaceHandler;

}

catch (ClassNotFoundException ex) {

throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +

namespaceUri + "] not found", ex);

}

catch (LinkageError err) {

throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +

namespaceUri + "]: problem with handler class file or dependent class", err);

}

}

}

然后继续看parseCustomElement方法handler.parse方法,<aop:config>标签又交给了ConfigBeanDefinitionParser来解析.

首先findParseForElement获取parser然后进行调用parse方法。


@Override

public BeanDefinition parse(Element element, ParserContext parserContext) {

return findParserForElement(element, parserContext).parse(element, parserContext);

}

我们来看看ConfigBeanDefinitionParser的parse方法:大致是注册一个bean,以及切点、增强、切面的解析(pointcunt\advisor\aspect)


@Override

public BeanDefinition parse(Element element, ParserContext parserContext) {

CompositeComponentDefinition compositeDef =

new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));

parserContext.pushContainingComponent(compositeDef);

configureAutoProxyCreator(parserContext, element);

List<Element> childElts = DomUtils.getChildElements(element);

for (Element elt: childElts) {

String localName = parserContext.getDelegate().getLocalName(elt);

if (POINTCUT.equals(localName)) {

parsePointcut(elt, parserContext);

}

else if (ADVISOR.equals(localName)) {

parseAdvisor(elt, parserContext);

}

else if (ASPECT.equals(localName)) {

parseAspect(elt, parserContext);

}

}

parserContext.popAndRegisterContainingComponent();

return null;

}

configureAutoProxyCreator(parserContext, element);一路跟踪到如下


public static void registerAspectJAutoProxyCreatorIfNecessary(

ParserContext parserContext, Element sourceElement) {

//注册一个AspectJAwareAdvisorAutoProxyCreator,

BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(

parserContext.getRegistry(), parserContext.extractSource(sourceElement));

//解析配置元素,决定代理的模式

useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);

//作为系统组件,把这个creator这个bean,放到Spring容器中,让Spring实力化,启动这个Creator

registerComponentIfNecessary(beanDefinition, parserContext);

}

在bean实例化完成后,会调用BeanPostProcessor的postProcessAfterInitialization方法在其父类AbstractAutoProxyCreator中实现,


@Override

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

if (bean != null) {

Object cacheKey = getCacheKey(bean.getClass(), beanName);

if (!this.earlyProxyReferences.contains(cacheKey)) {

return wrapIfNecessary(bean, beanName, cacheKey);

}

}

return bean;

}

跟踪wrapIfNecessary方法,会进入到createProxy方法


protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {

if (beanName != null && this.targetSourcedBeans.contains(beanName)) {

return bean;

}

if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {

return bean;

}

if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {

this.advisedBeans.put(cacheKey, Boolean.FALSE);

return bean;

}

// Create proxy if we have advice.

        // 意思就是如果该类有advice则创建proxy,

Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);

if (specificInterceptors != DO_NOT_PROXY) {

this.advisedBeans.put(cacheKey, Boolean.TRUE);

            // 1.通过方法名也能简单猜测到,这个方法就是把bean包装为proxy的主要方法,

Object proxy = createProxy(

bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));

this.proxyTypes.put(cacheKey, proxy.getClass());



            // 2.返回该proxy代替原来的bean

return proxy;

}

this.advisedBeans.put(cacheKey, Boolean.FALSE);

return bean;

}

createProxy方法中最后一句proxyFactory.getProxy会进入到DefaultAopProxyFactory的createAopProxy方法。


protected Object createProxy(

Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {

if (this.beanFactory instanceof ConfigurableListableBeanFactory) {

AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);

}

ProxyFactory proxyFactory = new ProxyFactory();

proxyFactory.copyFrom(this);

if (!proxyFactory.isProxyTargetClass()) {

if (shouldProxyTargetClass(beanClass, beanName)) {

proxyFactory.setProxyTargetClass(true);

}

else {

evaluateProxyInterfaces(beanClass, proxyFactory);

}

}

Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);

for (Advisor advisor : advisors) {

proxyFactory.addAdvisor(advisor);

}

proxyFactory.setTargetSource(targetSource);

customizeProxyFactory(proxyFactory);

proxyFactory.setFrozen(this.freezeProxy);

if (advisorsPreFiltered()) {

proxyFactory.setPreFiltered(true);

}

return proxyFactory.getProxy(getProxyClassLoader());

}


@Override

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {

if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {

Class<?> targetClass = config.getTargetClass();

if (targetClass == null) {

throw new AopConfigException("TargetSource cannot determine target class: " +

"Either an interface or a target is required for proxy creation.");

}

//如果bean的类是接口或者类是JDK内部的代理类,则使用JDK的动态代理类。

if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {

return new JdkDynamicAopProxy(config);

}

//其他情况使用CGLIB来实现。

return new ObjenesisCglibAopProxy(config);

}

else {

return new JdkDynamicAopProxy(config);

}

}

创建代理类后,其余过程与bean的生命周期基本一致。

2、注解配置 <aop:aspectj-autoproxy>标签使用分析

与aop:config标签大致类似,区别在于:

1、此标签交给AspectJAutoProxyBeanDefinitionParser去进行解析。

2、AspectJAutoProxyBeanDefinitionParser解析时候注册的是AnnotationAwareAspectJAutoProxyCreator的bean。

3、SpringBoot配置AOP

SpringBoot项目自动化配置是读取spring-boot-autoconfigure项目中的/META-INF/spring.factories的文件,以及json配置文件


org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\


{

      "name": "spring.aop.auto",

      "type": "java.lang.Boolean",

      "description": "Add @EnableAspectJAutoProxy.",

      "defaultValue": true

    },

我们看下AopAutoConfiguration


@Configuration

//依赖于EnableAspectJAutoProxy与Aspect与Advice

@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })

//spring.aop.auto=true此类才会进行加载

@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)

public class AopAutoConfiguration {

@Configuration

//proxyTargetClass默认为false

@EnableAspectJAutoProxy(proxyTargetClass = false)

@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = true)

public static class JdkDynamicAutoProxyConfiguration {

}

@Configuration

@EnableAspectJAutoProxy(proxyTargetClass = true)

@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = false)

public static class CglibAutoProxyConfiguration {

}

}

那我们继续,来看EnableAspectJAutoProxy注解


@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Import(AspectJAutoProxyRegistrar.class)

public @interface EnableAspectJAutoProxy {

/**

* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed

* to standard Java interface-based proxies. The default is {@code false}.

*/

boolean proxyTargetClass() default false;

/**

* Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}

* for retrieval via the {@link org.springframework.aop.framework.AopContext} class.

* Off by default, i.e. no guarantees that {@code AopContext} access will work.

* @since 4.3.1

*/

boolean exposeProxy() default false;

}

导入了AspectJAutoProxyRegistrar配置我们来看AspectJAutoProxyRegistrar源码


class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

/**

* Register, escalate, and configure the AspectJ auto proxy creator based on the value

* of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing

* {@code @Configuration} class.

*/

@Override

public void registerBeanDefinitions(

AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

AnnotationAttributes enableAspectJAutoProxy =

AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);

if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {

AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);

}

if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {

AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);

}

}

}

这里是不是很熟悉,相当于<aop:aspectj-autoproxy>标签使用,注册AnnotationAwareAspectJAutoProxyCreator。springboot加载过程就是这样。

仔细想想SpringBoot是基于注解的形式,那么肯定是有什么地方调用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

4、AopNamespaceHandler中init方法


public void init() {

// In 2.0 XSD as well as in 2.1 XSD.

registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());

registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());

registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

// Only in 2.0 XSD: moved to context namespace as of 2.1

registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());

}

一二行上面以及介绍过了,对于scoped-proxy可以理解为作用域代理。Spring中作用域默认是singleton.如果想使用其他除了单例模式以外的作用域,则需要添加scoped-proxy标签,默认走CGLIB代理。跟踪代码到ScopedProxyBeanDefinitionDecorator类中。


public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {

boolean proxyTargetClass = true;

if (node instanceof Element) {

Element ele = (Element) node;

if (ele.hasAttribute(PROXY_TARGET_CLASS)) {

proxyTargetClass = Boolean.valueOf(ele.getAttribute(PROXY_TARGET_CLASS));

}

}

// Register the original bean definition as it will be referenced by the scoped proxy

// and is relevant for tooling (validation, navigation).

BeanDefinitionHolder holder =

ScopedProxyUtils.createScopedProxy(definition, parserContext.getRegistry(), proxyTargetClass);

String targetBeanName = ScopedProxyUtils.getTargetBeanName(definition.getBeanName());

parserContext.getReaderContext().fireComponentRegistered(

new BeanComponentDefinition(definition.getBeanDefinition(), targetBeanName));

return holder;

}

再看其createScopedProxy方法


public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,

BeanDefinitionRegistry registry, boolean proxyTargetClass) {

String originalBeanName = definition.getBeanName();

BeanDefinition targetDefinition = definition.getBeanDefinition();

String targetBeanName = getTargetBeanName(originalBeanName);

// Create a scoped proxy definition for the original bean name,

// "hiding" the target bean in an internal target definition.

RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);

proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));

proxyDefinition.setOriginatingBeanDefinition(targetDefinition);

proxyDefinition.setSource(definition.getSource());

proxyDefinition.setRole(targetDefinition.getRole());

proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);

if (proxyTargetClass) {

targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);

// ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here.

}

else {

proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);

}

// Copy autowire settings from original bean definition.

proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());

proxyDefinition.setPrimary(targetDefinition.isPrimary());

if (targetDefinition instanceof AbstractBeanDefinition) {

proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);

}

// The target bean should be ignored in favor of the scoped proxy.

targetDefinition.setAutowireCandidate(false);

targetDefinition.setPrimary(false);

// Register the target bean as separate bean in the factory.

registry.registerBeanDefinition(targetBeanName, targetDefinition);

// Return the scoped proxy definition as primary bean definition

// (potentially an inner bean).

return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());

}

对于创建ScopedProxyFactoryBean要看其中的setBeanFactory方法


@Override

public void setBeanFactory(BeanFactory beanFactory) {

if (!(beanFactory instanceof ConfigurableBeanFactory)) {

throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);

}

ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;

this.scopedTargetSource.setBeanFactory(beanFactory);

ProxyFactory pf = new ProxyFactory();

pf.copyFrom(this);

pf.setTargetSource(this.scopedTargetSource);

Class<?> beanType = beanFactory.getType(this.targetBeanName);

if (beanType == null) {

throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName +

"': Target type could not be determined at the time of proxy creation.");

}

if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {

pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));

}

// Add an introduction that implements only the methods on ScopedObject.

ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());

pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));

// Add the AopInfrastructureBean marker to indicate that the scoped proxy

// itself is not subject to auto-proxying! Only its target bean is.

pf.addInterface(AopInfrastructureBean.class);

this.proxy = pf.getProxy(cbf.getBeanClassLoader());

}

spring-configured不太了解就不多说了,有了解的可以一起交流。

好了写到这里,喜欢的大家互动起来。

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

推荐阅读更多精彩内容