Spring AOP浅析(二) AOP实现分析

前文介绍了代理模式及实现,但想要实现一个完整的AOP框架还远远不够,接下来我们来分析一下Spring是如何实现AOP的。

1 AOP体系结构

下图AOP联盟定义的AOP体系结构,大致分为从使用到实现的三个层次。整篇文章都将按照这三个层次进行分析


1.1 层次3:语言和开发环境

  • 基础:可以视为待增强对象
  • 切面:通常包含对于基础的增强应用
  • 配置:指配置环境或编织配置,将基础和切面结合起来,从而实现对目标对象的编织实现

Spring使用Java语言来实现增强对象和切面,并为这两者提供配置环境;对于编织配置,可以使用IoC容器来完成,而Ioc恰恰是Spring的强项。

1.2 层次2:面向方面系统

面向方面系统为上层的语言与开发环境提供支持,将基础、切面和配置封装成面向方面中的逻辑模型。

1.3 层次1:底层编织实现模块

将编织逻辑进行实现的技术。
前文所述的两种动态代理技术就是在这个层面进行应用。

2 Spring AOP实现

我们从上到下的看看Spring如何实现AOP的。整个AOP框架逻辑流程很复杂,这里就只对在目标对象在单例模式下并使用JDK动态代理的AOP进行分析。

2.1 层次3:语言和开发环境

2.1.1 基础

在Spring中,基础就是你配置的Bean对象,这个Bean对象可以配置在XML中,也可以使用注解进行配置,如<bean id="realSubject" class="com.magicalwolf.proxy.RealSubject" />

2.1.2 切面

在Spring中,切面由切点和通知组成,由使用者进行定义.

PointCut切点

决定Advice通知应该作用于哪个连接点,也就是通过切点定义哪些方法需要增强。

PointCut接口是所有切点实现的基本接口,其中定义了MethodMatcher方法,用来判断当前方法是否需要增强。可以通过不同的方式进行判断,如JdkRegexpMethodPointcut类使用正则表达式进行匹配,还可以使用类限定名,切点表达式等方式进行匹配。

Advice通知

通知定义在连接点做什么,为切面增强提供织入接口。Advice接口是AOP联盟定义的统一接口,Spring对这个接口进行了细化和扩展,如BeforeAdvice,AfterAdvice,ThrowsAdvice

Advisor通知器

通知器将通知和切点结合起来,为Spring配置AOP容器提供便利。

2.1.3 配置

对基础和切面进行配置,使之对基础进行增强.Spring实现多种方式的配置,ProxyFactoryBean完成声明式配置,ProxyFactory完成编程式配置。AspectJProxyFactory将Spring和Aspectj集成。

2.1.4 使用ProxyFactoryBean的示例

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans"
    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">
    <!-- 目标对象 -->
    <bean id="realSubject" class="com.magicalwolf.proxy.RealSubject" />
    <!-- 通知 -->
    <bean id="requestAdvice" class="com.magicalwolf.spring.aop.RequestBeforeAdvice" />
    <!-- 通知器 -->
    <bean id="requestAdvisor"
        class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <property name="advice">
            <ref bean="requestAdvice" />
        </property>

        <property name="mappedName">
            <value>request</value>
        </property>
    </bean>
    <!-- 代理工厂 -->
    <bean id="requestAOP" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces" value="com.magicalwolf.proxy.Subject" />
        <property name="target">
            <ref bean="realSubject" />
        </property>
        <property name="interceptorNames">
            <list>
                <value>requestAdvisor</value>
            </list>
        </property>
    </bean>
</beans>

RequestBeforeAdvice.java

public class RequestBeforeAdvice implements MethodBeforeAdvice{
    private void before(String param) throws Throwable{
        if(!param.equals("magicalwolf"))
            throw new IllegalArgumentException();
    }
    @Override
    public void before(Method method, Object[] args, Object target)
            throws Throwable {
        before((String)args[0]);
    }
}

Main.java

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Subject proxy= (Subject) context.getBean("requestAOP");
        proxy.request("magicalwolf");
        proxy.request("hello");
    }
}

层次3主要定义了开发人员如何使用AOP,Spring在背后将进行逻辑模型的封装工作

2.2 层次2:面向方面系统

我们从ProxyFactoryBean出发,看看面向方面系统是如何对target目标起作用的。

2.2.1 ProxyFactoryBean的继承关系

  • ProxyFactoryBean类负责具体AOP代理对象的生成
  • ProxyCreateSupport类是创建AOP代理对象的一个辅助类
  • AdvisedSupport类封装了对通知和通知器相关的操作
  • ProxyConfig类为子类提供配置属性

2.2.2 生成代理对象跟踪

从FactoryBean中获取对象,是以getObject()方法为入口,在ProxyFactoryBean也是如此,getObject()方法对目标对象进行增强处理。

2.2.2.1 public Object getObject()

首先对通知器链进行初始化,生成代理对象时如果是单例模式调用getSingletonInstance(),此处只分析单例的情况

public Object getObject() throws BeansException {
    initializeAdvisorChain();//初始化通知器链(2.2.2.2)
    if (isSingleton()) {
        return getSingletonInstance();//生成Singleton的代理对象(2.2.2.3)
    }
    else {
        if (this.targetName == null) {
            logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
                    "Enable prototype proxies by setting the 'targetName' property.");
        }
        return newPrototypeInstance();
    }
}
2.2.2.2 private synchronized void initializeAdvisorChain()

初始化通知器链,此方法是线程安全的

private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
    if (this.advisorChainInitialized) {
        return;
    }
    /检查interceptorNames是否为空,interceptorNames是一个String[]
    if (!ObjectUtils.isEmpty(this.interceptorNames)) {
        //检查beanFactory是否为null,此处的beanFactory是Spring的Bean工厂,直接从容器中获得配置的通知器
        if (this.beanFactory == null) {
            throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " +
                    "- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames));
        }

        // Globals can't be last unless we specified a targetSource using the property...
        if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) &&
                this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) {
            throw new AopConfigException("Target required after globals");
        }

        // 通过Bean Name获得通知器
        for (String name : this.interceptorNames) {
            if (logger.isTraceEnabled()) {
                logger.trace("Configuring advisor or advice '" + name + "'");
            }
            //GLOBAL_SUFFIX = "*",说明这个通知器是全局通知器
            if (name.endsWith(GLOBAL_SUFFIX)) {
                if (!(this.beanFactory instanceof ListableBeanFactory)) {
                    throw new AopConfigException(
                            "Can only use global advisors or interceptors with a ListableBeanFactory");
                }
                addGlobalAdvisor((ListableBeanFactory) this.beanFactory,
                        name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
            }

            else {
                // 添加一个命名的通知器,检查是singleton还是prototype
                Object advice;
                if (this.singleton || this.beanFactory.isSingleton(name)) {
                    // 获得一个Advice或Advisor
                    advice = this.beanFactory.getBean(name);
                }
                else {
                    // It's a prototype Advice or Advisor: replace with a prototype.
                    // Avoid unnecessary creation of prototype bean just for advisor chain initialization.
                    advice = new PrototypePlaceholderAdvisor(name);
                }
                addAdvisorOnChainCreation(advice, name);//添加通知器到通知器列表(2.2.6)
            }
        }
    }

    this.advisorChainInitialized = true;
}
2.2.2.3 private synchronized Object getSingletonInstance()
private synchronized Object getSingletonInstance() {
    //singletonInstance为缓存的单例对象,如果存在就直接返回
    if (this.singletonInstance == null) {
        this.targetSource = freshTargetSource();//targetSource在AdvisedSupport中定义,用来获得目标对象
        if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
            //依靠AOP框架告诉我们哪个接口被代理
            Class<?> targetClass = getTargetClass();
            if (targetClass == null) {
                throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
            }
            //设置代理接口,在AdvisedSupport中定义了interfaces列表
            setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
        }
        super.setFrozen(this.freezeProxy);
        //使用ProxyFactory生成需要的Proxy
        this.singletonInstance = getProxy(createAopProxy());
        //getProxy(2.2.2.4),createAopProxy(2.2.2.5)
    }
    return this.singletonInstance;
}
2.2.2.4 protected Object getProxy(AopProxy aopProxy)

AopProxy是一个接口,有两个子类实现,一个是ObjenesisCglibAopProxy,一个是JdkDynamicProxy.分别通过CGLIB和JDK来生成需要的Proxy代理对象

protected Object getProxy(AopProxy aopProxy) {
    //通过AopProxy得到代理对象
    return aopProxy.getProxy(this.proxyClassLoader);
}
2.2.2.5 protected final synchronized AopProxy createAopProxy()

此方法在ProxyCreatorSupport中定义,具体是通过AopProxyFactory来获得AopProxy,AopProxyFactory默认的工厂实现是DefaultAopProxyFactory,生成哪一种AopProxy就在此类中定义

protected final synchronized AopProxy createAopProxy() {
    if (!this.active) {
        activate();
    }
    return getAopProxyFactory().createAopProxy(this);
}

DefaultAopProxyFactory中的createAopProxy方法

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.");
        }
        //如果是接口类使用JDK生成Proxy
        if (targetClass.isInterface()) {
            return new JdkDynamicAopProxy(config);
        }
        //否则用cglib
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

JdkDynamicAopProxy中的getObject方法,这里我们可以看到熟悉的Proxy.newProxyInstance()方法

public Object getProxy(ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
    }
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
2.2.2.6 private void addAdvisorOnChainCreation(Object next, String name)

namedBeanToAdvisor方法会将next对象包装为Advisor对象,并添加到Advisor集合中

private void addAdvisorOnChainCreation(Object next, String name) {
    Advisor advisor = namedBeanToAdvisor(next);
    if (logger.isTraceEnabled()) {
        logger.trace("Adding advisor with name '" + name + "'");
    }
    addAdvisor(advisor);
}

namedBeanToAdvisor方法中,会使用DefaultAdvisorAdapterRegistry的warp方法对next对象进行包装.通知器是由切点和通知组成,如果只配置了通知,则切点为默认的TruePointcut,它对任何方法的匹配都将返回true

public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
    //如果对象已经是Advisor直接返回
    if (adviceObject instanceof Advisor) {
        return (Advisor) adviceObject;
    }
    //检查类型
    if (!(adviceObject instanceof Advice)) {
        throw new UnknownAdviceTypeException(adviceObject);
    }
    Advice advice = (Advice) adviceObject;
    if (advice instanceof MethodInterceptor) {
        // 包装为默认切点通知器
        return new DefaultPointcutAdvisor(advice);
    }
    for (AdvisorAdapter adapter : this.adapters) {
        // 检查是否匹配
        if (adapter.supportsAdvice(advice)) {
            return new DefaultPointcutAdvisor(advice);
        }
    }
    throw new UnknownAdviceTypeException(advice);
}

通过以上的分析,我们已经得到了代理对象,至此层次2的工作已经完成了,层次3获得的配置模型,在配置逻辑的应用下向AOP模型转换,接下来该层次1的实现了

2.2 层次1:底层编织实现模块

在生成代理对象的时候,相关的拦截器已经配置完成,拦截器起作用是通过对方法进行回掉完成的。

2.2.1 使用JDK代理的实现

前文提到过,在JDK代理中方法回掉的入口是在invoke方法中。而JdkDynamicAopProxy实现了InvocationHandler接口,方法回掉逻辑也定义在其中.

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Class<?> targetClass = null;
    Object target = null;

    try {
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            // 目标没有实现自己的equals方法
            return equals(args[0]);
        }
        if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            // 目标没有实现自己的hashCodes方法
            return hashCode();
        }
        if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
                method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // 根据代理对象的配置调用服务,如果是Advised接口的实现类,则直接调用
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }

        Object retVal;

        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        // 有可能为null.尽可能减少拥有目标对象的时间,在这种情况下对象来自于对象池
        target = targetSource.getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }

        // 获得这个方法的连接器链
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);//2.2.2
        //如果没有拦截器链,则直接调用目标对象
        if (chain.isEmpty()) {
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
        }
        else {
            // 构造一个方法调用
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // 调用连接点的拦截器链(2.2.3)
            retVal = invocation.proceed();
        }

        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target && 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.
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // 重新保存旧的代理
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

invoke方法获取目标对象和拦截器链,并生成ReflectiveMethodInvocation对象,通过这个对象完成对AOP功能的封装。

2.2.2 获得方法的拦截器链

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, 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;
}

methodCache是一个集合Map<MethodCacheKey, List<Object>>,对方法的拦截器链进行缓存,如果不在缓存中,则生成并添加。这里使用DefaultAdvisorChainFactory来生成拦截器链

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
        Advised config, Method method, Class<?> targetClass) {

    //通过config获得配置好的advisor链,AdvisedSupport实现了Advised
    List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);
    //实际对象
    Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
    //判断是否符合配置要求
    boolean hasIntroductions = hasMatchingIntroductions(config, actualClass);
    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)) {
                MethodInterceptor[] interceptors = registry.getInterceptors(advisor);//将通知器适配成方法拦截
                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;
}

此方法有一个适配和注册的过程,它将Advice通知适配成Spring预先设计好的拦截器。适配和注册的工作是在GlobalAdvisorAdapterRegistry的getInterceptors()中完成的

public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
    List<MethodInterceptor> interceptors = new ArrayList<MethodInterceptor>(3);
    Advice advice = advisor.getAdvice();//获得通知
    if (advice instanceof MethodInterceptor) {//如果是MethodInterceptor则直接添加
        interceptors.add((MethodInterceptor) advice);
    }
    for (AdvisorAdapter adapter : this.adapters) {//遍历注册的适配器,检查是否匹配
        if (adapter.supportsAdvice(advice)) {
            interceptors.add(adapter.getInterceptor(advisor));
        }
    }
    if (interceptors.isEmpty()) {
        throw new UnknownAdviceTypeException(advisor.getAdvice());
    }
    return interceptors.toArray(new MethodInterceptor[interceptors.size()]);
}

GlobalAdvisorAdapterRegistry的构造函数中注册了三种适配器,注册过程就是将这三种适配器加入List集合

public DefaultAdvisorAdapterRegistry() {
    registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
    registerAdvisorAdapter(new AfterReturningAdviceAdapter());
    registerAdvisorAdapter(new ThrowsAdviceAdapter());
}

来看一下MethodBeforeAdviceAdapter

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
    @Override
    public boolean supportsAdvice(Advice advice) {//是否支持
        return (advice instanceof MethodBeforeAdvice);
    }
    @Override
    public MethodInterceptor getInterceptor(Advisor advisor) {//将Advice适配成Interceptor
        MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
        return new MethodBeforeAdviceInterceptor(advice);
    }
}

将Advice封装成了MethodBeforeAdviceInterceptor,此类中有invoke方法,会先调用advice的before方法

public Object invoke(MethodInvocation mi) throws Throwable {
    this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
    return mi.proceed();
}

至此,Spring AOP实现了对advice的织入,可以看到它将xml中配置的通知器适配成了拦截器

2.2.3 方法调用

之前讲到了拦截器的适配和注册,对呀没有拦截器的方法直接调用,有拦截器的方法会构造ReflectiveMethodInvocation,并沿着拦截器链进行调用。整个调用链的入口在proceed方法中

public Object proceed() throws Throwable {
    //从索引为-1的拦截器开始,并递增
    //如果拦截器迭代调用完成,则调用目标方法
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();//使用invokeJoinpointUsingReflection调用目标对象
    }
    //沿着拦截器链执行
    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        //对方法进行动态匹配,切点的匹配就在这里进行
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            // 动态匹配失败
            // 跳过这个拦截器调用下一个
            return proceed();
        }
    }
    else {
        // 这是一个拦截器,直接调用它
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

至此,完成对拦截器链及目标方法的调用

3 总结

3.1 层次3:语言与开发环境

Spring AOP使用Java语言,可以通过多种方式进行AOP配置,基础就是Bean对象,切面可以通过xml或注解进行声明.配置是由IoC容器完成的

3.2 层次2:面向方面系统

本文分析使用ProxyFactoryBean的情况,ProxyFactoryBean处理配置逻辑,生成代理对象。ProxyFactoryBean会先初始化通知器集合,再根据代理类型使用JdkDynamicProxyObjenesisCglibAopProxy生成代理对象

3.1 层次1:底层编织实现模块

使用JDK代理中,方法的回掉入口在invoke方法中,在invoke方法中实现了Advice的织入,以及目标方法的调用。
对Advice的织入,Spring预先设计好了拦截器,如MethodBeforeAdviceInterceptor,AfterReturningAdviceInterceptor,ThrowsAdviceInterceptor,将Advice适配成对应的拦截器,并将拦截器链缓存,此时完成了对Advice的织入
对目标方法的调用,如果在目标方法未配置拦截器,则直接调用目标方法,如果得到了拦截器链,则沿着拦截器链执行。在执行过程中如果是动态匹配的拦截器,则需要再次进行匹配,否则直接调用拦截器。

以上分析了Spring AOP的部分实现,不过整个AOP基本流程已经分析完成。有了基础再去看AOP的高级部分会更加容易

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 本博中关于spring的文章:Spring IOC和AOP原理,Spring事务原理探究,Spring配置文件属性...
    Maggie编程去阅读 4,098评论 0 34
  • 什么是Spring Spring是一个开源的Java EE开发框架。Spring框架的核心功能可以应用在任何Jav...
    jemmm阅读 16,445评论 1 133
  • title: Spring_AOP源码分析date: 2016-11-03 01:15:11categories:...
    raincoffee阅读 1,735评论 2 36
  • 前阵子回了趟老家,与多年未见的高中同学进行聚会。十多年未见,真是弹指一挥间,大家除了感叹时光的无情之外,更多则是惊...
    伊伊8899阅读 663评论 7 12