八间影、AOP源碼分析

尋找入口

Spring 的AOP 是通過接入BeanPostProcessor 后置處理器開始的,它是Spring IOC 容器經常使用到的一個特性晴氨,這個Bean 后置處理器是一個監(jiān)聽器康嘉,可以監(jiān)聽容器觸發(fā)的Bean 聲明周期事件。后置處理器向容器注冊以后籽前,容器中管理的Bean 就具備了接收IOC 容器事件回調的能力凄鼻。

BeanPostProcessor 的使用非常簡單,只需要提供一個實現(xiàn)接口BeanPostProcessor 的實現(xiàn)類聚假,然后在Bean 的配置文件中設置即可块蚌。

1、BeanPostProcessor 源碼

public interface BeanPostProcessor {
    //為在Bean 的初始化前提供回調入口
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    //為在Bean 的初始化之后提供回調入口
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

這兩個回調的入口都是和容器管理的Bean 的生命周期事件緊密相關膘格,可以為用戶提供在Spring IOC容器初始化Bean 過程中自定義的處理操作峭范。

2、AbstractAutowireCapableBeanFactory 類對容器生成的Bean 添加后置處理器

BeanPostProcessor 后置處理器的調用發(fā)生在Spring IOC 容器完成對Bean 實例對象的創(chuàng)建和屬性的依賴注入完成之后瘪贱,在對Spring 依賴注入的源碼分析過程中我們知道纱控,當應用程序第一次調用getBean()方法(lazy-init 預實例化除外)向Spring IOC 容器索取指定Bean 時觸發(fā)Spring IOC 容器創(chuàng)建Bean 實例對象并進行依賴注入的過程, 其中真正實現(xiàn)創(chuàng)建Bean 對象并進行依賴注入的方法是AbstractAutowireCapableBeanFactory 類的doCreateBean()方法菜秦,主要源碼如下:

    //真正創(chuàng)建Bean 的方法
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
            throws BeanCreationException {
        //創(chuàng)建Bean 實例對象
        ...
        Object exposedObject = bean;
        try {
            //對Bean 屬性進行依賴注入
            populateBean(beanName, mbd, instanceWrapper);
            //在對Bean 實例對象生成和依賴注入完成以后甜害,開始對Bean 實例對象
            //進行初始化,為Bean 實例對象應用BeanPostProcessor 后置處理器
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        } catch (Throwable ex) {
            if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
                throw (BeanCreationException) ex;
            } else {
                throw new BeanCreationException(
                        mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
            }
        }
        ...
        //為應用返回所需要的實例對象
        return exposedObject;
    }

從上面的代碼中我們知道球昨,為Bean 實例對象添加BeanPostProcessor 后置處理器的入口的是initializeBean()方法尔店。

3、initializeBean()方法為容器產生的Bean 實例對象添加BeanPostProcessor 后置處理器

同樣在AbstractAutowireCapableBeanFactory 類中,initializeBean()方法實現(xiàn)為容器創(chuàng)建的Bean實例對象添加BeanPostProcessor 后置處理器嚣州,源碼如下:

    //初始容器創(chuàng)建的Bean 實例對象鲫售,為其添加BeanPostProcessor 后置處理器
    protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
        //JDK 的安全機制驗證權限
        if (System.getSecurityManager() != null) {
        //實現(xiàn)PrivilegedAction 接口的匿名內部類
            AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                invokeAwareMethods(beanName, bean);
                return null;
            }, getAccessControlContext());
        } else {
            //為Bean 實例對象包裝相關屬性,如名稱该肴,類加載器情竹,所屬容器等信息
            invokeAwareMethods(beanName, bean);
        }
        Object wrappedBean = bean;
        //對BeanPostProcessor 后置處理器的postProcessBeforeInitialization
        //回調方法的調用,為Bean 實例初始化前做一些處理
        if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        }
        //調用Bean 實例對象初始化的方法匀哄,這個初始化方法是在Spring Bean 定義配置
        //文件中通過init-Method 屬性指定的
        try {
            invokeInitMethods(beanName, wrappedBean, mbd);
        } catch (Throwable ex) {
            throw new BeanCreationException(
                    (mbd != null ? mbd.getResourceDescription() : null),
                    beanName, "Invocation of init Method failed", ex);
        }
        //對BeanPostProcessor 后置處理器的postProcessAfterInitialization
        //回調方法的調用秦效,為Bean 實例初始化之后做一些處理
        if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        }
        return wrappedBean;
    }

    @Override
    //調用BeanPostProcessor 后置處理器實例對象初始化之前的處理方法
    public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
            throws BeansException {
        Object result = existingBean;
        //遍歷容器為所創(chuàng)建的Bean 添加的所有BeanPostProcessor 后置處理器
        for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
            //調用Bean 實例所有的后置處理中的初始化前處理方法,為Bean 實例對象在
            //初始化之前做一些自定義的處理操作
            Object current = beanProcessor.postProcessBeforeInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

    @Override
    //調用BeanPostProcessor 后置處理器實例對象初始化之后的處理方法
    public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
            throws BeansException {
        Object result = existingBean;
        //遍歷容器為所創(chuàng)建的Bean 添加的所有BeanPostProcessor 后置處理器
        for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
            //調用Bean 實例所有的后置處理中的初始化后處理方法涎嚼,為Bean 實例對象在
            //初始化之后做一些自定義的處理操作
            Object current = beanProcessor.postProcessAfterInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

BeanPostProcessor 是一個接口棉安,其初始化前的操作方法和初始化后的操作方法均委托其實現(xiàn)子類來實現(xiàn),在Spring 中铸抑,BeanPostProcessor 的實現(xiàn)子類非常的多贡耽,分別完成不同的操作,如:AOP 面向切面編程的注冊通知適配器鹊汛、Bean 對象的數(shù)據(jù)校驗蒲赂、Bean 繼承屬性、方法的合并等等刁憋,我們以最簡單的AOP 切面織入來簡單了解其主要的功能滥嘴。下面我們來分析其中一個創(chuàng)建AOP 代理對象的子類AbstractAutoProxyCreator 類。該類重寫了postProcessAfterInitialization()方法至耻。

選擇代理策略

進入postProcessAfterInitialization()方法若皱,我們發(fā)現(xiàn)調到了一個非常核心的方法wrapIfNecessary(),其源碼如下:

    @Override
    public Object postProcessAfterInitialization(@Nullable 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;
    }
    ...

    /**
     * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
     *
     * @param bean     the raw bean instance
     * @param beanName the name of the bean
     * @param cacheKey the cache key for metadata access
     * @return a proxy wrapping the bean, or the raw bean instance as-is
     */
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
        // 判斷是否不應該代理這個bean
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }
        /**
         *
         *判斷是否是一些InfrastructureClass 或者是否應該跳過這個bean尘颓。
         *所謂InfrastructureClass 就是指Advice/PointCut / Advisor 等接口的實現(xiàn)類走触。
         *shouldSkip 默認實現(xiàn)為返回false, 由于是protected 方法,子類可以覆蓋疤苹。
         */
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
        // 獲取這個bean 的advice
        // Create proxy if we have advice.
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName,
                null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            // 創(chuàng)建代理
            Object proxy = createProxy(
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
    ...

    /**
     * Create an AOP proxy for the given bean.
     *
     * @param beanClass            the class of the bean
     * @param beanName             the name of the bean
     * @param specificInterceptors the set of interceptors that is
     *                             specific to this bean (may be empty, but not null)
     * @param targetSource         the TargetSource for the proxy,
     *                             already pre-configured to access the bean
     * @return the AOP proxy for the bean
     * @see #buildAdvisors
     */
    protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
                                 @Nullable 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);
        proxyFactory.addAdvisors(advisors);
        proxyFactory.setTargetSource(targetSource);
        customizeProxyFactory(proxyFactory);
        proxyFactory.setFrozen(this.freezeProxy);
        if (advisorsPreFiltered()) {
            proxyFactory.setPreFiltered(true);
        }
        return proxyFactory.getProxy(getProxyClassLoader());
    }

整個過程跟下來互广,我發(fā)現(xiàn)最終調用的是proxyFactory.getProxy()方法。到這里我們大概能夠猜到proxyFactory 有JDK 和CGLib 的卧土,那么我們該如何選擇呢惫皱?最終調用的是DefaultAopProxyFactory的createAopProxy()方法:

    public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
        @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.");
                }
                if(targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                    return new JdkDynamicAopProxy(config);
                }
                return new ObjenesisCglibAopProxy(config);
            } else {
                return new JdkDynamicAopProxy(config);
            }
        }

        /**
         * Determine whether the supplied {@link AdvisedSupport} has only the
         * {@link org.springframework.aop.SpringProxy} interface specified
         * (or no proxy interfaces specified at all).
         */
        private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
            Class<?>[] ifcs = config.getProxiedInterfaces();
            return (ifcs.length == 0 || (ifcs.length == 1 &&
                    SpringProxy.class.isAssignableFrom(ifcs[0])));
        }
    }

調用代理方法

分析調用邏輯之前先上類圖,看看Spring 中主要的AOP 組件:


image.png

上面我們已經了解到Spring 提供了兩種方式來生成代理方式有JDKProxy 和CGLib尤莺。下面我們來研究一下Spring 如何使用JDK 來生成代理對象旅敷,具體的生成代碼放在JdkDynamicAopProxy 這個類中,直接上相關代碼:

    /**
     * 獲取代理類要實現(xiàn)的接口,除了Advised 對象中配置的,還會加上SpringProxy, Advised(opaque=false)
     * 檢查上面得到的接口中有沒有定義equals 或者hashcode 的接口
     * 調用Proxy.newProxyInstance 創(chuàng)建代理對象
     */
    @Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
        }
        Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }

通過注釋我們應該已經看得非常明白代理對象的生成過程颤霎,此處不再贅述媳谁。下面的問題是涂滴,代理對象生成了,那切面是如何織入的韩脑?

我們知道InvocationHandler 是JDK 動態(tài)代理的核心,生成的代理對象的方法調用都會委托到InvocationHandler.invoke()方法粹污。而從JdkDynamicAopProxy 的源碼我們可以看到這個類其實也實現(xiàn)了InvocationHandler段多,下面我們分析Spring AOP 是如何織入切面的,直接上源碼看invoke()方法:

    public Object invoke(Object proxy, Method Method, Object[] args) throws Throwable {
        MethodInvocation invocation;
        Object oldProxy = null;
        boolean setProxyContext = false;
        TargetSource targetSource = this.advised.targetSource;
        Object target = null;
        try {
            //eqauls()方法壮吩,具目標對象未實現(xiàn)此方法
            if (!this.equalsDefined && AopUtils.isEqualsMethod(Method)) {
                return equals(args[0]);
            }
            //hashCode()方法进苍,具目標對象未實現(xiàn)此方法
            else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(Method)) {
                return hashCode();
            } else if (Method.getDeclaringClass() == DecoratingProxy.class) {
                return AopProxyUtils.ultimateTargetClass(this.advised);
            }
            //Advised 接口或者其父接口中定義的方法,直接反射調用,不應用通知
            else if (!this.advised.opaque && Method.getDeclaringClass().isInterface() &&
                    Method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                return AopUtils.invokeJoinpointUsingReflection(this.advised, Method, args);
            }
            Object retVal;
            if (this.advised.exposeProxy) {
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
            }
            //獲得目標對象的類
            target = targetSource.getTarget();
            Class<?> targetClass = (target != null ? target.getClass() : null);
            //獲取可以應用到此方法上的Interceptor 列表
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(Method, targetClass);
            //如果沒有可以應用到此方法的通知(Interceptor),此直接反射調用Method.invoke(target, args)
            if (chain.isEmpty()) {
                Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(Method, args);
                retVal = AopUtils.invokeJoinpointUsingReflection(target, Method, argsToUse);
            } else {
                //創(chuàng)建MethodInvocation
                invocation = new ReflectiveMethodInvocation(proxy, target, Method, args, targetClass, chain);
                retVal = invocation.proceed();
            }
            Class<?> returnType = Method.getReturnType();
            if (retVal != null && retVal == target &&
                    returnType != Object.class && returnType.isInstance(proxy) &&
                    !RawTargetAccess.class.isAssignableFrom(Method.getDeclaringClass())) {
                retVal = proxy;
            } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
                throw new AopInvocationException(
                        "Null return value from advice does not match primitive return type for: " + Method);
            }
            return retVal;
        } finally {
            if (target != null && !targetSource.isStatic()) {
                targetSource.releaseTarget(target);
            }
            if (setProxyContext) {
                AopContext.setCurrentProxy(oldProxy);
            }
        }
    }

主要實現(xiàn)思路可以簡述為:首先獲取應用到此方法上的通知鏈(Interceptor Chain)鸭叙。如果有通知觉啊,則應用通知,并執(zhí)行JoinPoint沈贝;如果沒有通知杠人,則直接反射執(zhí)行JoinPoint。而這里的關鍵是通知鏈是如何獲取的以及它又是如何執(zhí)行的呢宋下?現(xiàn)在來逐一分析嗡善。首先,從上面的代碼可以看到学歧,通知鏈是通過Advised.getInterceptorsAndDynamicInterceptionAdvice()這個方法來獲取的罩引,我們來看下這個方法的實現(xiàn)邏輯:

    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method Method, @Nullable Class<?> targetClass) {
        MethodCacheKey cacheKey = new MethodCacheKey(Method);
        List<Object> cached = this.MethodCache.get(cacheKey);
        if (cached == null) {
            cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
                    this, Method, targetClass);
            this.MethodCache.put(cacheKey, cached);
        }
        return cached;
    }

通過上面的源碼我們可以看到, 實際獲取通知的實現(xiàn)邏輯其實是由AdvisorChainFactory 的getInterceptorsAndDynamicInterceptionAdvice()方法來完成的枝笨,且獲取到的結果會被緩存袁铐。下面來分析getInterceptorsAndDynamicInterceptionAdvice()方法的實現(xiàn):

    /**
     * 從提供的配置實例config 中獲取advisor 列表,遍歷處理這些advisor.如果是IntroductionAdvisor,
     * 則判斷此Advisor 能否應用到目標類targetClass 上.如果是PointcutAdvisor,則判斷
     * 此Advisor 能否應用到目標方法Method 上.將滿足條件的Advisor 通過AdvisorAdaptor 轉化成Interceptor 列表返回.
     */
    @Override
    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
            Advised config, Method Method, @Nullable Class<?> targetClass) {
        List<Object> interceptorList = new ArrayList<>(config.getAdvisors().length);
        Class<?> actualClass = (targetClass != null ? targetClass : Method.getDeclaringClass());
        //查看是否包含IntroductionAdvisor
        boolean hasIntroductions = hasMatchingIntroductions(config, actualClass);
        //這里實際上注冊一系列AdvisorAdapter,用于將Advisor 轉化成MethodInterceptor
        AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
        for (Advisor advisor : config.getAdvisors()) {
            if (advisor instanceof PointcutAdvisor) {
                PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
                if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
                    //這個地方這兩個方法的位置可以互換下
                    //將Advisor 轉化成Interceptor
                    MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
                    //檢查當前advisor 的pointcut 是否可以匹配當前方法
                    MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                    if (MethodMatchers.matches(mm, Method, actualClass, hasIntroductions)) {
                        if (mm.isRuntime()) {
                            for (MethodInterceptor interceptor : interceptors) {
                                interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
                            }
                        } else {
                            interceptorList.addAll(Arrays.asList(interceptors));
                        }
                    }
                }
            } else if (advisor instanceof IntroductionAdvisor) {
                IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
                if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
                    Interceptor[] interceptors = registry.getInterceptors(advisor);
                    interceptorList.addAll(Arrays.asList(interceptors));
                }
            } else {
                Interceptor[] interceptors = registry.getInterceptors(advisor);
                interceptorList.addAll(Arrays.asList(interceptors));
            }
        }
        return interceptorList;
    }

這個方法執(zhí)行完成后,Advised 中配置能夠應用到連接點(JoinPoint)或者目標類(Target Object)的Advisor 全部被轉化成了MethodInterceptor横浑,接下來我們再看下得到的攔截器鏈是怎么起作用的剔桨。

        if (chain.isEmpty()) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(Method, args);
            retVal = AopUtils.invokeJoinpointUsingReflection(target, Method, argsToUse);
        } else {
            //創(chuàng)建MethodInvocation
            invocation = new ReflectiveMethodInvocation(proxy, target, Method, args, targetClass, chain);
            retVal = invocation.proceed();
        }

從這段代碼可以看出, 如果得到的攔截器鏈為空徙融, 則直接反射調用目標方法领炫, 否則創(chuàng)建MethodInvocation,調用其proceed()方法张咳,觸發(fā)攔截器鏈的執(zhí)行帝洪,來看下具體代碼:

 public Object proceed() throws Throwable {
        //如果Interceptor 執(zhí)行完了,則執(zhí)行joinPoint
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();
        }
        Object interceptorOrInterceptionAdvice =
                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        //如果要動態(tài)匹配joinPoint
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        //動態(tài)匹配:運行時參數(shù)是否滿足匹配條件
        if (dm.MethodMatcher.matches(this.Method, this.targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            //動態(tài)匹配失敗時,略過當前Intercetpor,調用下一個Interceptor
            return proceed();
        }
    }
else {
        //執(zhí)行當前Intercetpor
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}

至此脚猾,通知鏈就完美地形成了葱峡。我們再往下來invokeJoinpointUsingReflection()方法,其實就是反射調用:

    public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method,
                                                        Object[] args) throws Throwable {
        // Use reflection to invoke the method.
        try {
            ReflectionUtils.makeAccessible(method);
            return method.invoke(target, args);
        } catch (InvocationTargetException ex) {
            // Invoked method threw a checked exception.
            // We must rethrow it. The client won't see the interceptor.
            throw ex.getTargetException();
        } catch (IllegalArgumentException ex) {
            throw new AopInvocationException("AOP configuration seems to be invalid: tried calling
                    method[" +
                    method + "] on target [" + target + "]", ex);
        } catch (IllegalAccessException ex) {
            throw new AopInvocationException("Could not access method [" + method + "]", ex);
        }
    }
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末龙助,一起剝皮案震驚了整個濱河市砰奕,隨后出現(xiàn)的幾起案子蛛芥,更是在濱河造成了極大的恐慌,老刑警劉巖军援,帶你破解...
    沈念sama閱讀 221,331評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仅淑,死亡現(xiàn)場離奇詭異,居然都是意外死亡胸哥,警方通過查閱死者的電腦和手機涯竟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來空厌,“玉大人庐船,你說我怎么就攤上這事〕案” “怎么了筐钟?”我有些...
    開封第一講書人閱讀 167,755評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赋朦。 經常有香客問我篓冲,道長,這世上最難降的妖魔是什么宠哄? 我笑而不...
    開封第一講書人閱讀 59,528評論 1 296
  • 正文 為了忘掉前任纹因,我火速辦了婚禮,結果婚禮上琳拨,老公的妹妹穿的比我還像新娘瞭恰。我一直安慰自己,他們只是感情好狱庇,可當我...
    茶點故事閱讀 68,526評論 6 397
  • 文/花漫 我一把揭開白布惊畏。 她就那樣靜靜地躺著,像睡著了一般密任。 火紅的嫁衣襯著肌膚如雪颜启。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,166評論 1 308
  • 那天浪讳,我揣著相機與錄音缰盏,去河邊找鬼。 笑死淹遵,一個胖子當著我的面吹牛口猜,可吹牛的內容都是我干的。 我是一名探鬼主播透揣,決...
    沈念sama閱讀 40,768評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼济炎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了辐真?” 一聲冷哼從身側響起须尚,我...
    開封第一講書人閱讀 39,664評論 0 276
  • 序言:老撾萬榮一對情侶失蹤崖堤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后耐床,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體密幔,經...
    沈念sama閱讀 46,205評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,290評論 3 340
  • 正文 我和宋清朗相戀三年撩轰,在試婚紗的時候發(fā)現(xiàn)自己被綠了胯甩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,435評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡钧敞,死狀恐怖蜡豹,靈堂內的尸體忽然破棺而出麸粮,到底是詐尸還是另有隱情溉苛,我是刑警寧澤,帶...
    沈念sama閱讀 36,126評論 5 349
  • 正文 年R本政府宣布弄诲,位于F島的核電站愚战,受9級特大地震影響,放射性物質發(fā)生泄漏齐遵。R本人自食惡果不足惜寂玲,卻給世界環(huán)境...
    茶點故事閱讀 41,804評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望梗摇。 院中可真熱鬧拓哟,春花似錦、人聲如沸伶授。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽糜烹。三九已至违诗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間疮蹦,已是汗流浹背诸迟。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留愕乎,地道東北人阵苇。 一個月前我還...
    沈念sama閱讀 48,818評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像感论,于是被迫代替她去往敵國和親慎玖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,442評論 2 359