Spring AOP 實現(xiàn)源碼分析

1. AOP 概念

AOP(Aspect Oriented Programming)浆兰,即面向切面編程层亿。

  • 連接點(JoinPoint)
    程序執(zhí)行的某個特定位置:如類開始初始化前搭儒、類初始化后提鸟、類某個方法調用前伏伐、調用后、方法拋出異常后鸠补。一個類或一段程序代碼擁有一些具有邊界性質的特定點,這些點中的特定點就稱為“連接點”嘀掸。Spring僅支持方法的連接點紫岩,即僅能在方法調用前、方法調用后睬塌、方法拋出異常時以及方法調用前后這些程序執(zhí)行點織入增強泉蝌。
  • 切點(Pointcut)
    每個類具有多個連接點,如果一個類擁有15個方法,那么這些方法都是連接點,連接點相當于數據庫中的記錄揩晴,而切點相當于查詢條件勋陪。切點和連接點不是一對一的關系,一個切點可以匹配多個連接點硫兰。Spring AOP的規(guī)則解析引擎負責切點所設定的查詢條件诅愚,找到對應的連接點。其實確切地說劫映,不能稱之為查詢連接點违孝,因為連接點是方法執(zhí)行前、執(zhí)行后等包括方位信息的具體程序執(zhí)行點泳赋,而切點只定位到某個方法上雌桑,所以如果希望定位到具體連接點上,還需要提供方位信息祖今。
  • 增強(Advice)
    增強是織入到目標類連接點上的一段程序代碼校坑,在Spring中拣技,增強除用于描述一段程序代碼外,還擁有另一個和連接點相關的信息耍目,這便是執(zhí)行點的方位膏斤。結合執(zhí)行點方位信息切點信息,我們就可以找到特定的連接點制妄。
  • 通知器(Advisor)
    當我們完成切面增強設計(Advice)和切入點的設計(Pointcut),需要一個對象把他們結合起來掸绞,Advisor 就是起到這個作用,通過Advisor 耕捞,可以確定在哪個Pointcut 使用哪個Advice衔掸。所以一個Advisor包含一個Advice 和 一個Pointcut 信息。

2. 一些疑問

  1. Spring AOP 增強的代理類 在什么時候創(chuàng)建 俺抽?
  2. Spring AOP 怎樣為一個類 和 方法 匹配 增強的 敞映?
  3. Spring AOP 是如何 協(xié)調 前置通知 后置通知 異常通知 返回通知的?
  4. 切面類 可以被 AOP增強么磷斧?

3. 注冊 AnnotationAwareAspectJAutoProxyCreator

AnnotationAwareAspectJAutoProxyCreator是用來處理 當前項目當中 所有 AspectJ 注解的切面類振愿。以及所有的 Spring Advisor ,同時 也是一個 BeanPostProcessor 弛饭。所以 想了解Spring AOP 如何實現(xiàn)冕末,就要先分析 AnnotationAwareAspectJAutoProxyCreator,那么他從何而來侣颂,是如何注冊到容器內的档桃。
注冊AnnotationAwareAspectJAutoProxyCreator方式:

  1. 使用 EnableAspectJAutoProxy注解,改注解 又被 @Import 注解注釋,向容器中注冊了AspectJAutoProxyRegistrar憔晒,最后由 AspectJAutoProxyRegistrar 向容器中注冊 AnnotationAwareAspectJAutoProxyCreator藻肄。
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            //向容器注冊 AnnotationAwareAspectJAutoProxyCreator
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        //對proxyTargetClass 屬性的支持,切換JDK代理 和 CGLIB 代理
        if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
        }
        //是否對 AopContext.currentPoxy支持拒担。
        if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
            AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
        }
    }
}
  1. 使用 @EnableAutoConfiguration注解嘹屯。改注解會向容器注冊Spring.factories文件中聲明的類,其中AopAutoConfiguration是對Spring AOP自動配置的支持从撼。
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
    @Configuration
    最終還是通過 EnableAspectJAutoProxy 注冊AnnotationAwareAspectJAutoProxyCreator
    @EnableAspectJAutoProxy(proxyTargetClass = false)
    判斷條件 州弟,可以在 application.properties 中 配置。
    @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 {
    }
}
  1. xml 標簽
    Spring 采用 自定義標簽 <aop:aspectj-autoproxy /> 注冊AnnotationAwareAspectJAutoProxyCreator

4. AnnotationAwareAspectJAutoProxyCreator的初始化

AnnotationAwareAspectJAutoProxyCreator初始化 主要初始一些 組件對象谋逻。

組件包括:

  • AspectJAdvisorFactory
    根據AspectJ 注釋的切面類 創(chuàng)建 Spring AOP Advisors 的工廠接口呆馁。Advisor增強器的創(chuàng)建 都是由AspectJAdvisorFactory完成。
  • BeanFactoryAspectJAdvisorsBuilder
    從項目中 查找所有的切面類毁兆,然后使用 AspecJAdvisorFactory 創(chuàng)建 Advisors浙滤。
  • advisorRetrievalHelper
    對Xml 配置Advisor 的支持。從容器中檢索 所有的 Advisor 類型气堕。

5 . 創(chuàng)建代理類過程

由于 AnnotationAwareAspectJAutoProxyCreator 是一個 BeanPostProcessor 實現(xiàn)類纺腊,Spring 會在對一個對象的初始化前后執(zhí)行 BeanPostProcssor 的接口方法畔咧。Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;就是 創(chuàng)建增強代理對象的方法。該方法被AnnotationAwareAspectJAutoProxyCreator的父類AbstractAutoProxyCreator實現(xiàn)揖膜。

  • postProcessAfterInitialization
@Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    //bean 就是需要 被增強的類誓沸。
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            //防止 bean 多次被增強留攒。
            if (!this.earlyProxyReferences.contains(cacheKey)) {
            //如果有必要 就進行封裝撕阎,有沒有必要主要取決于 bean 是否需要被增強。
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }
  • wrapIfNecessary
    對給定bean 就行封裝轮纫。
/**
     *如果有必要 封裝 bean
     */
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;
        }
        //如果是一個 組件類趁仙,組件類包括Advice洪添,Pointcut,Advisor,AopInfrastructureBean實現(xiàn)類,和 Aspect注解注釋的類雀费。所以 切面類 自己不會被AOP 增強干奢。
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
        // 為bean 獲取和 匹配合適的 增強器 
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        //如果 沒有 匹配到增強器 那么就不用創(chuàng)建增強代理類。
        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;
    }
5.1 獲取增強器
  • getAdvicesAndAdvisorsForBean(為bean獲取和匹配合適的增強器)
    getAdvicesAndAdvisorsForBean ---> findEligibleAdvisors:

    為bean獲取和匹配增強器分為兩步:

5.1.1 獲取到所有的增強器
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    
    //獲取所有的增強器
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
    //匹配合適的增強器
        List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
        extendAdvisors(eligibleAdvisors);
        if (!eligibleAdvisors.isEmpty()) {
            eligibleAdvisors = sortAdvisors(eligibleAdvisors);
        }
        return eligibleAdvisors;
    }
  • findCandidateAdvisors 獲取到所有的增強器
    調用AnnotationAwareAspectjAutoProxyCreator 的 findCandidateAdvisors
    @Override
    protected List<Advisor> findCandidateAdvisors() {
       // 對xml 配置 Advisor 的支持.
       List<Advisor> advisors = super.findCandidateAdvisors();
       // 搜索容器中所有 @Aspecj 注解申明的 切面類盏袄,構建增強器
       advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
       return advisors;
    }
    
  • aspectJAdvisorsBuilder.buildAspectJAdvisors()
    代碼比較多忿峻,就不粘出來了,大致邏輯就是
  1. 獲取容器中已經注冊的BeanNames
  2. 遍歷 所有已經注冊的Bean,查找 @AspectJ
  3. 通過AspectJAdvisorFactory獲取Advisor辕羽。怎么獲取的 就不解釋了逛尚。
5.1.2 匹配合適的增強器

匹配合適的增強器,Spring 使用了 AopUtils.canApply刁愿。

匹配邏輯就是:

遍歷 所有增強器黑低,采用增強器的 Poincut 對一個對象的所有方法進行匹配,一旦有方法匹配到酌毡,就返回true.

對方法的匹配需要遍歷 所有方法 一一就行匹配。

5.2 創(chuàng)建 代理類

通過 ProxyFactory 創(chuàng)建動態(tài)代理類蕾管,創(chuàng)建之前 先配置ProxyFactory,設置目標對象枷踏,設置是否對目標對象代理(會影響采用JDK代理或者Cglib代理),設置增強器集合掰曾,等旭蠕。

配置ProxyFactory完成之后就是獲取代理類了,

調用ProxyFactory.getProxy:

public Object getProxy(ClassLoader classLoader) {
    //先獲取 AopProxy,AopProxy有JDK實現(xiàn)和 Cglib實現(xiàn)兩種。
        return createAopProxy().getProxy(classLoader);
}

判斷采取JDK代理 或Cglib代理旷坦。
判斷條件:

  1. Optimize: 是否采用了 激進的優(yōu)化策略掏熬,該優(yōu)化僅支持 Cglib代理
  2. ProxyTargetClass: 代理目標類,代理目標類 就是采用 子類繼承的方式創(chuàng)建代理秒梅,所以也是Cglib代理旗芬,可以通過。
  3. hasNoUserSuppliedProxyInterfaces:判斷是否是實現(xiàn)了接口捆蜀,如果沒有必須采用Cglib代理疮丛。
    所以如果我們想在項目中 采用Cglib代理的話 application.properties中配置:
    spring.aop.proxy-target-class=false幔嫂,或者使用注解配置proxyTargetClass = true .
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    //判斷采用什么代理類型。
    //Optimize 是否采用了 激進的優(yōu)化策略誊薄,該優(yōu)化僅支持 Cglib代理
    //ProxyTargetClass 代理目標類履恩,代理目標類 就是采用 子類繼承的方式創(chuàng)建代理,所以也是Cglib代理呢蔫,可以通過
    // 判斷是否是實現(xiàn)了接口切心,如果沒有必須采用Cglib代理。
        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代理片吊。
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
           
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

  • 拿JdkDynamicAopProxy來說绽昏。

獲取代理類:

JDK動態(tài)代理的關鍵是 InvocationHandler,JdkDynamicAopProxy實現(xiàn)了InvocationHandler接口定鸟。

@Override
    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, true);
        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
        //獲取代理類而涉,其中InvocationHanler 是 this,就是JdkDynamicAopProxy。
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }

6.增強方法的執(zhí)行

增強方法的執(zhí)行 是AOP的核心所在联予,理解Spring Aop 是如何 協(xié)調 各種通知 的執(zhí)行啼县,是理解的關鍵。

通知 分為前置 后置 異常 返回 環(huán)繞通知沸久,在一個方法中每種通知的執(zhí)行時機不同季眷,協(xié)調他們之間執(zhí)行順序很重要。

但是Spring AOP 采用了很聰明的方法讓各種各樣的通知準確有序的工作卷胯。

  • JdkDynamicAopProxy.invoker

由于invoker 方法很大子刮,我刪除了大部分的代碼,保留幾處關鍵代碼:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //省略了 對 hashCode equals 等方法的處理....
    //exposeProxy屬性的支持窑睁。
            if (this.advised.exposeProxy) {
                // Make invocation available if necessary.
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
            }
            // 獲取目標對象類
            target = targetSource.getTarget();
            if (target != null) {
                targetClass = target.getClass();
            }
      // 獲取攔截器鏈 這里this.advised 就是AnnotationAwareAspectJAutoProxyCreator
      List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
     //如果 沒有攔截器鏈 則直接執(zhí)行挺峡。
      if (chain.isEmpty()) {
         Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
         retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
      }
      else {    
          //開始執(zhí)行攔截器鏈
         invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
         retVal = invocation.proceed();
      }
    //返回執(zhí)行結果。
      return retVal;
  
}
6.1 創(chuàng)建攔截器鏈

攔截器鏈InterceptorAndDynamicMethodMatcher 類 和 MethodInterceptor 類型的集合担钮。

InterceptorAndDynamicMethodMatcher 封裝了 MethodInterceptor 和 MethodMather 橱赠,作用就是在執(zhí)行時進行再次匹配。

創(chuàng)建攔截器的過程就是 對所有 適合 目標類的 Advisor進行再一次篩選箫津。對匹配的Advisor封裝成 MethodInterceptor狭姨。通過MethodInterceptor 統(tǒng)一 增強方法的調用。

6.2 執(zhí)行攔截器鏈

使用ReflectiveMethodInvocation 對連接器鏈進行封裝苏遥。通過process 方法觸發(fā)攔截器開始執(zhí)行饼拍。

public Object proceed() throws Throwable {
        //  判斷攔截器鏈 是否執(zhí)行結束,如果執(zhí)行結束執(zhí)行目標方法田炭。
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();
        }
        //獲取下一個 需要執(zhí)行的 攔截器
        Object interceptorOrInterceptionAdvice =
        this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        //如果需要執(zhí)行時 進行匹配
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            InterceptorAndDynamicMethodMatcher dm =
                    (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
            //如果匹配师抄,并不清楚 為什么還要在這里 進行再次匹配。
            if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) 
            //注意這里 將ReflectiveMethodInvocation 自己當參數 教硫,傳入調用司澎。
                return dm.interceptor.invoke(this);
            }
            else {
            //遞歸的調用
                return proceed();
            }
        }
        else {
            //如果 MethodInterceptor 
            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
        }
    }

查看代碼并沒有發(fā)現(xiàn)任何的關于協(xié)調前置欺缘,后置各種通知的代碼。其實所有的協(xié)調工作都是由MethodInterceptor 自己維護挤安。

  • 前置MethodInterceptor(MethodBeforeAdviceInterceptor)的invoke
    @Override
  public Object invoke(MethodInvocation mi) throws Throwable {
        //激活 前置增強方法
      this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
        //繼續(xù)調用下一個 攔截器谚殊。
      return mi.proceed();
  }

  • 后置MethodInterceptor(AspectJAfterAdvice)

    @Override
      public Object invoke(MethodInvocation mi) throws Throwable {
          try {
                //繼續(xù)調用一下攔截器。
              return mi.proceed();
          }
          finally {
                //在finally 里面激活 后置增強方法
              invokeAdviceMethod(getJoinPointMatch(), null, null);
          }
      }
    

    通過對比前置 和 后置 攔截器蛤铜,可以發(fā)現(xiàn) 具體協(xié)調各種通知 的 邏輯 實際是利用了 遞歸的思想嫩絮。
    后置攔截器為例: 先執(zhí)行 遞歸方法 proceed()。最后從遞歸方法返回的時候 最后調用 后置方法围肥。

7. 模擬攔截器調用

為了更好理解 攔截器調用剿干,自己實現(xiàn)了一個簡單的攔截器鏈調用過程。

7.1 準備工作
  • 接口

    //方法調用   
    interface MethodInvocation{
             Object process() throws Throwable;
        }
    //方法攔截器
        interface MethodInterceptor{
            Object invoke(MethodInvocation mi) throws Throwable;
        }
    
  • 實現(xiàn)

    //后置增強方法的 攔截器
    
    class AfterMethodInterceptor implements MethodInterceptor{
            String identification;
            public AfterMethodInterceptor(String identification){
                this.identification = identification;
            }
            @Override
            public Object invoke(MethodInvocation mi) throws Throwable {
                try {
                    return mi.process();
                }finally {
                    System.out.println("執(zhí)行后置通知"+identification);
                }
            }
        }
    //前置 的 方法攔截器 
        class BeforMethodInterceptor implements MethodInterceptor{
            String identification;
            public BeforMethodInterceptor(String identification){
                this.identification = identification;
            }
            @Override
            public Object invoke(MethodInvocation mi) throws Throwable {
                System.out.println("執(zhí)行前置通知"+identification);
                return mi.process();
            }
        }
    // 默認的 方法調用實現(xiàn)
     class DefaultMethodInvacation implements MethodInvocation {
            List<MethodInterceptor> chian;
            Object target; //目標對象
            Method method; //目標方法
            Object[] args; //方法參數
            int currentChianIndex; //記錄攔截器鏈當前執(zhí)行位置
            public  DefaultMethodInvacation(List<MethodInterceptor> chian,Object target,Method method,Object[] args){
                this.chian = chian;
                this.method = method;
                this.target = target;
                this.args = args;
            }
            @Override
            public Object process() throws Throwable{
                Object value ;
                //如果 攔截器 執(zhí)行完畢 執(zhí)行 目標方法
                if(currentChianIndex == chian.size()){
                    value = method.invoke(target, args);
                    return value;
                }
                //獲取下一個 攔截器
                MethodInterceptor methodInterceptor = chian.get(currentChianIndex++);
                return methodInterceptor.invoke(this);
            }
        }
    //目標對象
    class TargetObj{
        //目標方法
          public void targetMethod(){
               System.out.println("-----目標方法執(zhí)行----");
           }
        }
    
7.2 測試代碼
@Test
    public void aopchain() throws Throwable {
        List<MethodInterceptor> chain = Lists.newArrayList();
        //攔截器鏈穆刻,穿插這 放入 前置 和 后置 攔截器 置尔。
        chain.add(new AfterMethodInterceptor("1"));
        chain.add(new BeforMethodInterceptor("1"));
        chain.add(new AfterMethodInterceptor("2"));
        chain.add(new BeforMethodInterceptor("2"));
        chain.add(new AfterMethodInterceptor("3"));
        chain.add(new BeforMethodInterceptor("3"));
        //目標對象
        TargetObj targetObj = new TargetObj();
        //目標方法
        Method method = TargetObj.class.getMethod("targetMethod");
        DefaultMethodInvacation defaultMethodInvacation = new DefaultMethodInvacation(chain, targetObj, method, null);
        //執(zhí)行攔截器鏈
        defaultMethodInvacation.process();
    }
7.3 執(zhí)行結果

雖然 前置 和 后置 都是 無序的 存放在 攔截器鏈中,但是 前置 和 后置 都在各自的位置執(zhí)行氢伟。

執(zhí)行前置通知1
執(zhí)行前置通知2
執(zhí)行前置通知3
-----目標方法執(zhí)行----
執(zhí)行后置通知3
執(zhí)行后置通知2
執(zhí)行后置通知1
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末榜轿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子朵锣,更是在濱河造成了極大的恐慌谬盐,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诚些,死亡現(xiàn)場離奇詭異飞傀,居然都是意外死亡,警方通過查閱死者的電腦和手機诬烹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門砸烦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人绞吁,你說我怎么就攤上這事外冀。” “怎么了掀泳?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長西轩。 經常有香客問我员舵,道長,這世上最難降的妖魔是什么藕畔? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任马僻,我火速辦了婚禮,結果婚禮上注服,老公的妹妹穿的比我還像新娘韭邓。我一直安慰自己措近,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布女淑。 她就那樣靜靜地躺著瞭郑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸭你。 梳的紋絲不亂的頭發(fā)上屈张,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音袱巨,去河邊找鬼阁谆。 笑死,一個胖子當著我的面吹牛愉老,可吹牛的內容都是我干的场绿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嫉入,長吁一口氣:“原來是場噩夢啊……” “哼焰盗!你這毒婦竟也來了?” 一聲冷哼從身側響起劝贸,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤姨谷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后映九,有當地人在樹林里發(fā)現(xiàn)了一具尸體梦湘,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年件甥,在試婚紗的時候發(fā)現(xiàn)自己被綠了捌议。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡引有,死狀恐怖瓣颅,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情譬正,我是刑警寧澤宫补,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站曾我,受9級特大地震影響粉怕,放射性物質發(fā)生泄漏。R本人自食惡果不足惜抒巢,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一贫贝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦稚晚、人聲如沸崇堵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸳劳。三九已至,卻和暖如春幸逆,著一層夾襖步出監(jiān)牢的瞬間棍辕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工还绘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留楚昭,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓拍顷,卻偏偏與公主長得像抚太,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子昔案,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理尿贫,服務發(fā)現(xiàn),斷路器踏揣,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • 本文是我自己在秋招復習時的讀書筆記庆亡,整理的知識點,也是為了防止忘記捞稿,尊重勞動成果又谋,轉載注明出處哦!如果你也喜歡娱局,那...
    波波波先森閱讀 12,294評論 6 86
  • **** AOP 面向切面編程 底層原理 代理U煤ァ!衰齐! 今天AOP課程1任斋、 Spring 傳統(tǒng) AOP2、 Spri...
    luweicheng24閱讀 1,369評論 0 1
  • title: Spring_AOP源碼分析date: 2016-11-03 01:15:11categories:...
    raincoffee閱讀 1,754評論 2 36
  • 本博中關于spring的文章:Spring IOC和AOP原理耻涛,Spring事務原理探究废酷,Spring配置文件屬性...
    Maggie編程去閱讀 4,104評論 0 34