Spring源碼解讀(3)AOP-切面類的注解處理

1侍瑟、概述

????Aop是面向接口的,也即是面向方法的丙猬,實現(xiàn)是在IOC的基礎上丢习,Aop可以攔截指定的方法并且對方法增強,而且無需侵入到業(yè)務代碼中淮悼,使業(yè)務與非業(yè)務處理邏輯分離,比如Spring的事務揽思,通過事務的注解配置袜腥,Spring會自動在業(yè)務方法中開啟、提交業(yè)務,并且在業(yè)務處理失敗時羹令,執(zhí)行相應的回滾策略鲤屡,aop的實現(xiàn)主要包括了兩個部分:

  • 匹配符合條件的方法(Pointcut)

  • 對匹配的方法增強(JDK代理cglib代理)

????spring針對xml配置和配置自動代理的Advisor有很大的處理差別,在IOC中主要是基于XML配置分析的福侈,在AOP的源碼解讀中酒来,則主要從自動代理的方式解析,分析完注解的方式肪凛,再分析基于xml的方式堰汉。

2、案例準備

????下面是spring aop的用法 也是用于源碼分析的案例

????切面類:TracesRecordAdvisor

@Aspect
@Component
public class TracesRecordAdvisor {
    
    @Pointcut("execution(* spring.action.expend.aop.services.*.*(..))")
    public void expression() {
    }

    @Before("expression()")
    public void beforePrint()
    {
        System.out.println("進入服務,記錄日志開始....");
    }

    @AfterReturning("expression()")
    public void afterPrint()
    {
        System.out.println("退出服務,記錄日志退出.....");
    }
}

????xml配置: aop的注解啟用只需要在xml中配置這段代碼即可伟墙,這個是作為入口

    <aop:aspectj-autoproxy/>

????服務類:PayServiceImpl 使用jdk代理 所以要有一個接口

@Service
public class PayServiceImpl implements PayService {
    public void payMoneyService() {
        System.out.println("付款服務正在進行...");
    }
}

????測試方法:

   @Test
    public void springAopTestService() {
        ClassPathXmlApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring-aop.xml");
        PayService payService= (PayService) applicationContext.getBean("payServiceImpl");
        payService.payMoneyService();
    }

????執(zhí)行結(jié)果:

進入服務,記錄日志開始....
付款服務正在進行...
退出服務,記錄日志退出.....

????從上面的執(zhí)行結(jié)果看,payMoneyService方法的確是被增強了翘鸭。

3、BeanFactoryPostProcessor

????在讀spring源碼時戳葵,我想首先來看下BeanFactoryPostProcessorBeanPostProcess,這兩個接口都是在spring通過配置文件或者xml獲取bean聲明生成完BeanDefinition后允許我們對生成BeanDefinition進行再次包裝的入口就乓。

首先看下BeanFactoryPostProcessor的定義

public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}

????方法postProcessBeanFactory的參數(shù)為ConfigurableListableBeanFactory拱烁,前文說過beanFactory用來獲取bean的生蚁,而ConfigurableListableBeanFactory繼承接口SingletonBeanRegistryBeanFactroy,所以可以訪問到已經(jīng)生成過的BeanDefinitions集合戏自,如果某個類實現(xiàn)該接口邦投,spring會注冊這個類,然后執(zhí)行這個類的postProcessBeanFactory方法浦妄,以便我們對BeanDefinition進行擴展尼摹。

對于BeanFactoryPostProcessor只做簡單的介紹,只是說明在Spring中剂娄,我們可以修改生成后的BeanDefinition蠢涝,這里住下看下Spring是如何注冊BeanFactoryPostProcessor并執(zhí)行postProcessBeanFactory的。

    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            prepareRefresh();
             //核心方法1
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            prepareBeanFactory(beanFactory);
            try {
                postProcessBeanFactory(beanFactory);
                 //核心方法2 執(zhí)行BeanFactoryPostProcessor
                invokeBeanFactoryPostProcessors(beanFactory);
                //核心方法 3 注冊BeanPostProcessor
                registerBeanPostProcessors(beanFactory);
                // Initialize message source for this context.
                initMessageSource();
                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();
                // Initialize other special beans in specific context subclasses.
                onRefresh();
                // Check for listener beans and register them.
                registerListeners();
                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);
                // Last step: publish corresponding event.
                finishRefresh();
            }
            catch (BeansException ex) {
                 ............
                throw ex;
            }
            finally {
                 ............
                resetCommonCaches();
            }
        }
    }

????核心方法1obtainFreshBeanFactory就是前兩篇所說的生成BeanDefinition的入口阅懦,invokeBeanFactoryPostProcessors核心方法2就是執(zhí)行BeanFactoryPostProcessor接口的方法和二。

    protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    }

????通過方法getBeanFactoryPostProcessors獲取注冊BeanFactoryPostProcessor,然后來看看如何添加一個處理器

    @Override
    public void addBeanFactoryPostProcessor(BeanFactoryPostProcessor beanFactoryPostProcessor) {
        this.beanFactoryPostProcessors.add(beanFactoryPostProcessor);
    }

????對于方法invokeBeanFactoryPostProcessors不再往下看了耳胎,里面的方法大致先對BeanFactoryPostProcessor進行排序惯吕,排序的標準是是否實現(xiàn)了PriorityOrdered,然后根據(jù)設置的order大小指定執(zhí)行順序怕午,生成一個排序集合和一個普通的集合废登,最后執(zhí)行invokeBeanFactoryPostProcessors

    private static void invokeBeanFactoryPostProcessors(
            Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {

        for (BeanFactoryPostProcessor postProcessor : postProcessors) {
             //執(zhí)行到自定義的BeanFactoryPostProcessor
            postProcessor.postProcessBeanFactory(beanFactory);
        }
    }

????這個方法就會循環(huán)先前注冊的BeanFactoryPostProcessor集合,然后執(zhí)行postProcessBeanFactory郁惜。

4堡距、BeanPostProcess

????與BeanFactoryPostProcessor相比,BeanPostProcess就重要得多了,因為Spring的注解羽戒、AOP等都是通過這個接口的方法攔截執(zhí)行的缤沦,它貫穿了Bean創(chuàng)建過程的整個生命周期,在IOC階段易稠,Spring只注冊BeanPostProcess缸废,執(zhí)行則放到了Bean的實例化創(chuàng)建階段。

首先看下BeanPostProcessor的接口定義

public interface BeanPostProcessor {
    //在bean創(chuàng)建 屬性賦值之后  Aware接口執(zhí)行之后執(zhí)行
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    //在init-method afterPropertiesSet 執(zhí)行之后執(zhí)行
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

????在bean的聲明周期中驶社,下面的序列是bean創(chuàng)建后要執(zhí)行的接口和方法順序:

實例化(autowireConstructor或者instantiateBean)---------------屬性初始化(populateBean)-------------Aware接

口(如果bean實現(xiàn)了的話)--------------------------BeanPostProcess.postProcessBeforeInitialization------------------

--PostConstructInitializingBean.afterPropertiesSet-----BeanPostProcess.postProcessAfterInitialization

????其中通過注解引入依賴的方式就是在AutowiredAnnotationBeanPostProcessor這個類中實現(xiàn)的企量,而接下來要分析的Spring Aop也是從這里開始的,這個類叫AnnotationAwareAspectJAutoProxyCreator,

5衬吆、NameSpaceHanlder

????在Spring中梁钾,任何的技術(shù)都是在IOC的基礎上進行的,Aop也不例外逊抡,程序會首先讀取xml配置文件姆泻,然后對讀取到的標簽先查找命名空間,然后找對應的NameSpaceHandler冒嫡,最終調(diào)用parse方法解析標簽拇勃。

????aop標簽的解析,使用純注解的方式aop:aspectj-autoproxy和使用aop:config的配置解析不太一樣孝凌,具體表現(xiàn)在生成PointCut和生成Before方咆、AfterAround等切面類時蟀架,使用aop:config的方式會為這些注解生成一個BeanDefinition瓣赂,而這個BeanDefinition的構(gòu)造函數(shù)是由3個BeanDefinition組成,表明這個類是合成類片拍,即synthetic這個屬性為true煌集。然后跟解析普通的bean一樣,生成這些實例對象捌省,后面的過程就跟是用純注解的方式相同了苫纤,接下來的分析是基于純注解分析的,也就是解析從解析aop:aspectj-autoproxy這個標簽開始纲缓。

????前面的xml文件的標簽解析是通過parseDefaultElement方法解析默認的<bean>標簽的卷拘,而我們在配置文件里面配置了啟動自動代理的方式<aop:aspectj-autoproxy/>,當Spring讀取到這個標簽祝高,則會走parseCustomElement(root)這個方法了栗弟,這個方法的源碼不再解析,主要完成的功能如下:

  • 獲取element的nameSpaceUri,根據(jù)nameSpaceUri找到NameSpaceHanlder

  • 調(diào)用NameSpaceHanlder的parse方法解析element

????下面是NameSpaceHanlder接口的定義

public interface NamespaceHandler {
    void init();
    BeanDefinition parse(Element element, ParserContext parserContext);
    BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext       parserContext);
}

????這里面的init方法是我們初始化操作的工闺,這里可以完成對指定的標簽設置解析器乍赫,然后再parse方法里面找到指定標簽的解析器颓屑,然后調(diào)用該解析器的parse方法解析標簽,后面會重點看這兩個方法耿焊。

????再來看下Spring如何加載NameSpaceHanlder的,Spring首先會取查找項目空間下目錄META-INF/的所有spring.handlers文件遍搞,這個文件是在Spring依賴的jar下面罗侯,在核心jar包都會由這個文件,aop的jar包路徑下文件內(nèi)容為:spring.handlers

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

????發(fā)現(xiàn)這里面存儲的是一個key溪猿,value,key是aop的nameSpaceUri钩杰,value是AopNamespaceHandler,從這個類名上就能發(fā)現(xiàn)該類實現(xiàn)了NamespaceHandler诊县,肯定也就實現(xiàn)了init和parse方法讲弄,所以解析<aop:aspectj-autoproxy/>的任務就由AopNamespaceHandler的parse完成。

查看AopNamespaceHandler的init方法

@Override
    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());
    }

上面的代碼就很清晰了依痊,<aop:config>標簽由ConfigBeanDefinitionParser處理避除,<aop:aspectj-autoproxy/>則由AspectJAutoProxyBeanDefinitionParser這個類處理,這兩種處理其實對應了自動代理和通過xml配置的處理方式胸嘁,然后會調(diào)用AspectJAutoProxyBeanDefinitionParserparse方法

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
        extendBeanDefinition(element, parserContext);
        return null;
    }

????這個方法其實就是為了注冊一個AnnotationAwareAspectJAutoProxyCreator類瓶摆,然后AOP的所有處理邏輯都會交給這個類處理,由于這個類的實現(xiàn)了BeanPostProcessor,所以這個類的入口就是BeanPostProcessor接口的兩個方法:

  • postProcessBeforeInitialization
  • postProcessAfterInitialization

6、Spring Aop 源碼解讀前奏

????上面分析了性宏,當spring讀取xml文件遇到<aop:aspectj-autoproxy/>會找到AopNamespaceHandler這個處理類群井,然后這個類又將這個標簽委托給了AspectJAutoProxyBeanDefinitionParser類,最終調(diào)用這個類得parse方法毫胜,parse方法未做分析书斜,其實這個方法的目的很簡單,就是注冊AnnotationAwareAspectJAutoProxyCreator這個類酵使,這個類實現(xiàn)了BeanPostProcessorInstantiationAwareBeanPostProcessor接口荐吉,最終在實例化bean對象也就是執(zhí)行BeanFactory.getBean(beanName)的過程中,會調(diào)用這兩個接口的方法(執(zhí)行順序如下):

InstantiationAwareBeanPostProcessor先執(zhí)行:

postProcessBeforeInstantiation(Class<?> beanClass, String beanName)

postProcessAfterInstantiation(Object bean, String beanName)

BeanPostProcessor再執(zhí)行:

postProcessBeforeInitialization(Object bean, String beanName)

Object postProcessAfterInitialization(Object bean, String beanName)

????AOP的實現(xiàn)基本上是在這兩個方法中進行的凝化,所以就從這里來看Spring是如何實現(xiàn)AOP的稍坯,Spring的AOP代理目前支持方法的增強,看源碼目前好像也支持了屬性的增強了搓劫。

????讀取源碼前首先來分析一下方法增強的原理瞧哟,有助于我們讀取源碼時緊緊抓住主線。首先第一個問題枪向,如果我們想對一個類的方法進行增強勤揩,我們應該怎么做呢?

????這種業(yè)務需求可以通過代理實現(xiàn)秘蛔,在方法執(zhí)行前陨亡,攔截這個方法傍衡,并且加入要執(zhí)行增強的邏輯,最后再執(zhí)行目標方法负蠕。下面是Spring用的兩種代理方式:

JDK代理:我們可以通Proxy類獲取一個目標類的代理對象蛙埂,但JDK代理要求被代理的類必須實現(xiàn)接口,所以是基于接口的代理遮糖。

cglib代理:如果目標類沒有接口绣的,使用cglib代理,是由asm封裝的欲账,直接操作類得字節(jié)碼屡江,效率也很高。

????由于在生產(chǎn)業(yè)務中赛不,我們不可能對所有的類都執(zhí)行增強惩嘉,所以還需要一個選擇器,將符合條件的bean進行增強踢故,Spring使用了PointCut接口文黎,通過該接口的getMethodMatcher方法獲取一個方法匹配器,然后通過matches方法匹配到目標類對象的目標方法執(zhí)行增強操作畴椰。mathcer匹配規(guī)則就是通過Spring 配置的expression表達式了臊诊。

????所以在分析源碼的時,要圍繞這兩方面進行:

  • 匹配切點方法(構(gòu)建切入點表達式類和切面類)

  • 創(chuàng)建代理對象

????這兩方面在Spring的實現(xiàn)里非常復雜斜脂,尤其是第一步匹配切點方法過程抓艳,這個過程中,Spring會將@Aspect注解類的@Before帚戳,@After玷或,@Around@Pointcut等注解都封裝成待執(zhí)行的切面方法類片任,然后通過方法匹配器匹配到的要增強的方法前后執(zhí)行切面方法類偏友,達到方法增強的目的。

????第二階段对供,創(chuàng)建代理對象默認是通過JDK代理實現(xiàn)配置位他,<aop:aspectj-autoproxy proxy-target-class="true">這樣配置可以指定使用cglib代理。

7产场、注解切面代理類

????上面分析了真正實現(xiàn)AOP功能的是AnnotationAwareAspectJAutoProxyCreator,由于這個類實現(xiàn)了BeanPostProcessorInstantiationAwareBeanPostProcessor鹅髓,所以在創(chuàng)建一個bean的時候,會進入到這兩個接口的方法京景,這兩個接口包含了四個方法窿冯,方法執(zhí)行順序上面已經(jīng)分析過了,來看看這個類的類圖:

image

????類圖上比較重要的接口就是右上角實現(xiàn)的兩個接口确徙,在bean創(chuàng)建的生命周期過程中醒串,會校驗當前容器中是否注冊了實現(xiàn)了這兩個接口的類执桌,如果有則調(diào)用接口的方法,前面的分析中在解析<aop:aspectj-autoproxy/>時芜赌,將這個類注冊到了容器中仰挣,而且上面也羅列了這兩個接口中四個方法的調(diào)用順序,在這個類中完成主要功能的2個方法及其執(zhí)行順序:

InstantiationAwareBeanPostProcessor先執(zhí)行:

postProcessBeforeInstantiation(Class<?> beanClass, String beanName)

BeanPostProcessor再執(zhí)行:

Object postProcessAfterInitialization(Object bean, String beanName)

????postProcessBeforeInstantiation方法主要是找出注解了Advice的類缠沈,并將Advice的類使用了@Before椎木,@After@Around博烂、@Pointcut,@AfterThrowing等注解的方法封裝成一個一個類放入到緩存中供匹配到的類生成代理用。postProcessAfterInitialization主要是匹配符合條件的目標類對象漱竖,然后生成代理的過程,接下來就按順序分析這兩個方法完成的功能禽篱。

8、處理Aspect注解類

    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        //構(gòu)建一個緩存key
        Object cacheKey = getCacheKey(beanClass, beanName);
        if (beanName == null || !this.targetSourcedBeans.contains(beanName)) {
             //如果當前beanClass的緩存key 存在于Class為Advise的緩存中馍惹,表示當前的beanClass是Adivse類
             //而且不需要生成代理躺率。
            if (this.advisedBeans.containsKey(cacheKey)) {
                return null;
            }
             //核心校驗:1 當前類是否是AOP的基礎類 2、當前類是否應該跳過不生成代理
            if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return null;
            }
        }
        //這部分主要是用于實現(xiàn)了TargetSource接口的bean万矾,然后從getTarget中獲取對象 創(chuàng)建代理
        if (beanName != null) {
            TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
            if (targetSource != null) {
                this.targetSourcedBeans.add(beanName);
                Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
                Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
                this.proxyTypes.put(cacheKey, proxy.getClass());
                return proxy;
            }
        }
        return null;
    }

????這個方法主要是先為beanClass生成一個緩存的key悼吱,這個beanClass如果是FactoryBean,則按照工廠類的命名規(guī)則命名良狈,否則用beanName命名后添,然后用剛才生成的key判斷beanClass是否已經(jīng)存在于Advice的緩存集合中,如果已經(jīng)存在則代表該類是切面類而且已經(jīng)被處理過了薪丁,后續(xù)處理不會為該類生成代理遇西,如果沒有沒處理過,則會調(diào)用下面的方法校驗該類是否是AOP的基礎類 严嗜,總之這個方法作用就是將AOP相關(guān)操作的切面類和基礎類放入到緩存中粱檀,當為bean生成代理的時候,忽略advice緩存中的AOP切面類和基礎類漫玄,下面是具體校驗過程:

AnnotationAwareAspectJAutoProxyCreator重寫了該方法

@Override
protected boolean isInfrastructureClass(Class<?> beanClass) {
//調(diào)用父類的isInfrastructureClass判斷是否是aop基礎類
//校驗當前類是否使用@Aspect注解
return (super.isInfrastructureClass(beanClass) || this.aspectJAdvisorFactory.isAspect(beanClass));
}

父類的isInfrastructureClass方法

    protected boolean isInfrastructureClass(Class<?> beanClass) {
        boolean retVal = Advice.class.isAssignableFrom(beanClass) ||
                Advisor.class.isAssignableFrom(beanClass) ||
                AopInfrastructureBean.class.isAssignableFrom(beanClass);
        if (retVal && logger.isTraceEnabled()) {
            logger.trace("Did not attempt to auto-proxy infrastructure class [" + beanClass.getName() + "]");
        }
        return retVal;
    }

????里面isAssignableFrom表示當前類是否允許被設置為beanClass類對象茄蚯,可以以此判斷beanClass是否是Advice類,所以這個方法的校驗目的就是判斷當前正在創(chuàng)建目標類是否是AOP的基礎類睦优,即該類是否是Advice,Advisor或者實現(xiàn)了AopInfrastructureBean接口渗常。該方法調(diào)用父類的isInfrastructureClass判斷是否是aop基礎類,然后再校驗當前類是否使用@Aspect注解刨秆,目的只有一個凳谦,如果是Advice切面相關(guān)的類不做任何處理,直接放入advice緩存即可衡未。

然后再來看shouldSkip(beanClass, beanName)

    @Override
    protected boolean shouldSkip(Class<?> beanClass, String beanName) {
        //查找當前已經(jīng)生成的所有Advisor切面類  不展開分析
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
        for (Advisor advisor : candidateAdvisors) {
            if (advisor instanceof AspectJPointcutAdvisor) {
                if (((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) {
                    return true;
                }
            }
        }
        return super.shouldSkip(beanClass, beanName);
    }

????這個方法主要是校驗當前正在創(chuàng)建bean的beanName是否屬于已經(jīng)創(chuàng)建好的切面類緩存中尸执,如果是則加入到advices緩存中家凯,不再處理。其中findCandidateAdvisors()會查找當前容器中生成的所有實現(xiàn)了Advisor的類如失,Spring會將@Before绊诲,@After@Around等生成一個繼承了Advisor類對象存儲到緩存中供后續(xù)使用,這一部分時Spring AOP前半段的核心內(nèi)容褪贵,后續(xù)都會圍繞著如何將切面類的注解生成Adisor類探索掂之。

AnnotationAwareAspectJAutoProxyCreator重寫了findCandidateAdvisors方法,所以會執(zhí)行到該方法:

@Override
protected List<Advisor> findCandidateAdvisors() {
   //通過父類的方法查找所有容器中的Advisor類脆丁,也就是基于xml配置的<aop:before/>生成的
   List<Advisor> advisors = super.findCandidateAdvisors();
   //查找通過注解的方式生成Advisor類
   advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
   return advisors;
}

????這個方法會首先調(diào)用父類的findCandidateAdvisors方法用于獲取通過xml文件配置生成的Advisor世舰,也就是通過<aop:before>,<aop:after>等生成的,然后調(diào)用通過注解方式即@Before槽卫,@After跟压,@Around@Pointcut,@AfterThrowing生成的advisor歼培,可以說震蒋,這兩個方法分別處理了基于xml配置文件的方式和基于注解的配置方式,因為所有的分析都是基于AnnotationAwareAspectJAutoProxyCreator這個類進行的躲庄,所以在這個地方會先獲取配置文件的查剖,再生成基于注解類的Advisor,這樣就將基于xml配置的和基于注解的配置都會解析到噪窘。

看下this.aspectJAdvisorsBuilder.buildAspectJAdvisors()

    public List<Advisor> buildAspectJAdvisors() {
        List<String> aspectNames = null;
        synchronized (this) {
            aspectNames = this.aspectBeanNames;
            if (aspectNames == null) {
                List<Advisor> advisors = new LinkedList<Advisor>();
                aspectNames = new LinkedList<String>();
                //從beanDefinitions中獲取所有的beanName
                String[] beanNames =
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);
                for (String beanName : beanNames) {
                      //如果beanName不符合配置的 <aop:include name="***"/>
                      //忽略這個bean上所有的切面方法
                    if (!isEligibleBean(beanName)) {
                        continue;
                    }
                    Class<?> beanType = this.beanFactory.getType(beanName);
                    if (beanType == null) {
                        continue;
                    }
                      //如果當前beanType是一個切面類 則將該切面類相關(guān)信息封裝起來
                    if (this.advisorFactory.isAspect(beanType)) {
                        aspectNames.add(beanName);
                        AspectMetadata amd = new AspectMetadata(beanType, beanName);
                        if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {                   
//將切面信息放入到分裝到MetadataAwareAspectInstanceFactory 生成一個AspectMetadata
MetadataAwareAspectInstanceFactory factory =new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
//獲取容器中所有Advisor類 需要進入這個方法詳細分析
List<Advisor> classAdvisors=this.advisorFactory.getAdvisors(factory);
                            if (this.beanFactory.isSingleton(beanName)) {
                                   //單例加入緩存
                                this.advisorsCache.put(beanName, classAdvisors);
                            }
                            else {
                                   //非單例  將工廠加入緩存
                                this.aspectFactoryCache.put(beanName, factory);
                            }
                            advisors.addAll(classAdvisors);
                        }
                        else {
                            // 非單例  將生成Advisor的工廠類加入到緩存
                            if (this.beanFactory.isSingleton(beanName)) {
                                throw new IllegalArgumentException("Bean with name '" + beanName +
                                        "' is a singleton, but aspect instantiation model is not singleton");
                            }
                            MetadataAwareAspectInstanceFactory factory =
                                    new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
                            this.aspectFactoryCache.put(beanName, factory);
                            advisors.addAll(this.advisorFactory.getAdvisors(factory));
                        }
                    }
                }
                this.aspectBeanNames = aspectNames;
                return advisors;
            }
        }
        ..............
    }

????這個方法主要的任務其實就是獲取類得類型為Aspect的切面類笋庄,然后獲取切面類方法的所有注解并將注解轉(zhuǎn)換成Advisor類返回,主要步驟為:

  • 獲取容器中所有的BeanDefinition的beanName

  • 根據(jù)beanName倔监,或者beanClass无切,匹配符合規(guī)則的Aspect切面類,通過<aop:include>配置的規(guī)則

  • 獲取Aspect切面類的所有切面方法封裝成Advisor對象返回丐枉。

  • 將獲取到的所有Advisor放入到緩存中哆键。

????這個方法代碼雖然很多,但是核心的是this.advisorFactory.getAdvisors(factory)瘦锹,即第三個步驟籍嘹,這個方法將會獲取到切面類的所有切面方法,并封裝成Advisor弯院,getAdvisors是一個接口辱士,ReflectiveAspectJAdvisorFactory實現(xiàn)了這個接口,下面代碼是其實現(xiàn)邏輯:

    @Override
    public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
         //獲取切面類Class
        Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
        //獲取切面類的beanName
        String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
        validate(aspectClass);
        //進一步對AspectMetadata封裝 里面包含了切面類的信息
        MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
                new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
        List<Advisor> advisors = new LinkedList<Advisor>();
         //獲取切面類中沒有使用Pointcut注解的方法
        for (Method method : getAdvisorMethods(aspectClass)) {
             //檢查該方法是否是切面方法听绳, 如果是成Advisor類返回
            Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
            if (advisor != null) {
                advisors.add(advisor);
            }
        }
        //如果沒有切面方法 設置一個空的
        if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
            Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
            advisors.add(0, instantiationAdvisor);
        }
        //處理屬性字段 Spring支持到了屬性的增強
        for (Field field : aspectClass.getDeclaredFields()) {
            Advisor advisor = getDeclareParentsAdvisor(field);
            if (advisor != null) {
                advisors.add(advisor);
            }
        }
        return advisors;
    }

????這個方法首先已經(jīng)將切面類信息封裝到AspectMetadata的類再次封裝到MetadataAwareAspectInstanceFactory颂碘,然后獲取切面類的所有沒有使用Pointcut注解的方法,調(diào)用getAdvisor獲取這個方法使用的切面注解椅挣,生成對應的Advisor類头岔。 至于PointCut的處理則是再后面的getAdvisor中處理的塔拳。

9、獲取切面類的Advisor

????獲取Advisor類的方法為getAdvisor峡竣,首先來看下這個方法的參數(shù):

//切面類的切面方法  這里可能就是 beforePrint()
Method  candidateAdviceMethod 
//獲取AspectMetadata的實例工廠(可以獲取切面的類所有信息)
MetadataAwareAspectInstanceFactory aspectInstanceFactory
//切面的排序
int declarationOrderInAspect
//切面類的beanName 這里是tracesRecordAdvisor
 String aspectName

????上面的參數(shù)中可以獲取到切面類和切面方法靠抑,這樣就可以獲得一個Advisor對象,然后還需要一個切入點表達式PointCut用來匹配符合條件的方法适掰,攔截到目標方法后颂碧,就可以執(zhí)行Adivsor增強方法了颗品。 來看看創(chuàng)建Advisor的過程缕坎,這里假設MethodTracesRecordAdvisor類的beforePrint方法周崭,也就是我們測試案例中創(chuàng)建使用了@Before注解的切面方法:

@Override
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
      int declarationOrderInAspect, String aspectName) {

   validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
   //獲取pointCut,這里實際上獲得的是 expression()這個方法對應了pointCut的內(nèi)容
   AspectJExpressionPointcut expressionPointcut = getPointcut(
         candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
   if (expressionPointcut == null) {
      return null;
   }
    //創(chuàng)建advisor
   return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
         this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

????看看getPointCut方法如何獲取到exression過程需要嵌套很多步驟愤兵,這里不展開了,簡單看下如何將查找到的值設置到表達式中的:

private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
   //
   AspectJAnnotation<?> aspectJAnnotation =
         AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
   if (aspectJAnnotation == null) {
      return null;
   }

   AspectJExpressionPointcut ajexp =
         new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
   //將上面生成的AspectJAnnotation 解析出的expression方法放入到表達式中
   //
   ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
   return ajexp;
}

????這里需要關(guān)注下上面的findAspectJAnnotationOnMethod方法:

protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
    //看到了我們熟悉的切面方法注解页慷,這里的beforePrint使用@Before注解
   Class<?>[] classesToLookFor = new Class<?>[] {
         Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};
   for (Class<?> c : classesToLookFor) {
      AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) c);
      if (foundAnnotation != null) {
         return foundAnnotation;
      }
   }
   return null;
}

????這個方法就是查找切面方法是否使用了Before, Around, After察郁,AfterReturning, AfterThrowing,Pointcut注解待逞,如果使用了受楼,則返回一個AspectJAnnotation對象,里面有一個annotation的泛型對象呼寸,這個泛型對象就是被設置為這些注解的值艳汽,而且還會獲得這些注解里面配置的pointcut表達式內(nèi)容,如果是引用的表達式方法对雪,則將方法參數(shù)設置到pointcutExpression這個屬性中河狐。

????解析完切面方法的注解后現(xiàn)在再回過頭來看看如何創(chuàng)建一個advisor實例:

public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut,
      Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory,
      MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
   this.declaredPointcut = declaredPointcut;
   this.aspectJAdviceMethod = aspectJAdviceMethod;
   this.aspectJAdvisorFactory = aspectJAdvisorFactory;
   this.aspectInstanceFactory = aspectInstanceFactory;
   this.declarationOrder = declarationOrder;
   this.aspectName = aspectName;
    //切面類是否是懶加載
   if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
      // Static part of the pointcut is a lazy type.
      Pointcut preInstantiationPointcut = Pointcuts.union(
            aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);

      // Make it dynamic: must mutate from pre-instantiation to post-instantiation state.
      // If it's not a dynamic pointcut, it may be optimized out
      // by the Spring AOP infrastructure after the first evaluation.
      this.pointcut = new PerTargetInstantiationModelPointcut(
            this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);
      this.lazy = true;
   }
   else {
      this.pointcut = this.declaredPointcut;
      this.lazy = false;
       //最終會執(zhí)行到這里獲取一個advice
      this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
   }
}

10、為切面方法創(chuàng)建Advice

????上面方法的最后一句instantiateAdvice(this.declaredPointcut)會創(chuàng)建一個advice瑟捣,具體是調(diào)用getAdvice方法獲炔鲆铡:

@Override
    public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
            MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
         //獲取切面類對象,這里是TracesRecordAdvisor
        Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
        validate(candidateAspectClass);
          //核心點1:獲取切面注解迈套,這里得方法是 beforePrint 使用了@Before注解
        AspectJAnnotation<?> aspectJAnnotation =
                AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
        if (aspectJAnnotation == null) {
            return null;
        }
            ....................
        AbstractAspectJAdvice springAdvice;
        //核心點2:根據(jù)注解轉(zhuǎn)換后的 將注解生成不同的Advice類捐祠。
        switch (aspectJAnnotation.getAnnotationType()) {
            case AtBefore:
                springAdvice = new AspectJMethodBeforeAdvice(
                        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                break;
            case AtAfter:
                springAdvice = new AspectJAfterAdvice(
                        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                break;
            case AtAfterReturning:
                springAdvice = new AspectJAfterReturningAdvice(
                        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
                if (StringUtils.hasText(afterReturningAnnotation.returning())) {
                    springAdvice.setReturningName(afterReturningAnnotation.returning());
                }
                break;
            case AtAfterThrowing:
                springAdvice = new AspectJAfterThrowingAdvice(
                        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
                if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
                    springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
                }
                break;
            case AtAround:
                springAdvice = new AspectJAroundAdvice(
                        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                break;
            case AtPointcut:
                //這里對PointCut不做處理
                if (logger.isDebugEnabled()) {
                    logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
                }
                return null;
            default:
                throw new UnsupportedOperationException(
                        "Unsupported advice type on method: " + candidateAdviceMethod);
        }

        // 將切面類信息配置到SpringAdvice中
        springAdvice.setAspectName(aspectName);
        springAdvice.setDeclarationOrder(declarationOrder);
        String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
        if (argNames != null) {
            springAdvice.setArgumentNamesFromStringArray(argNames);
        }
        springAdvice.calculateArgumentBindings();
        return springAdvice;
    }

????首先來看看核心點1,上面其實已經(jīng)看過了桑李, 但是上面的方法作用僅僅是為了獲取注解上的exression表達式的踱蛀,這里再調(diào)用一遍就是為注解生成Advice類的,目的就是獲取切面注解與AspectJAnnotation的映射類贵白。

protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
    //看到了我們熟悉的切面方法注解率拒,這里的beforePrint使用@Before注解
   Class<?>[] classesToLookFor = new Class<?>[] {
         Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};
   for (Class<?> c : classesToLookFor) {
      AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) c);
      if (foundAnnotation != null) {
         return foundAnnotation;
      }
   }
   return null;
}

????這個方法就是查找切面方法是否實現(xiàn)了Before, Around, AfterAfterReturning, AfterThrowing,Pointcut注解禁荒,如果實現(xiàn)了猬膨,則返回一個AspectJAnnotation對象,里面有一個annotation的泛型對象呛伴,這個泛型對象就是被設置為這些注解的值勃痴。最終這些對象會被轉(zhuǎn)換成下面的對象存入AspectJAnnotation中:

static {
   //會將注解轉(zhuǎn)換成后面的AspectJAnnotationType枚舉的類谒所。 
   annotationTypes.put(Pointcut.class,AspectJAnnotationType.AtPointcut);
   annotationTypes.put(After.class,AspectJAnnotationType.AtAfter);
   annotationTypes.put(AfterReturning.class,AspectJAnnotationType.AtAfterReturning);
   annotationTypes.put(AfterThrowing.class,AspectJAnnotationType.AtAfterThrowing);
   annotationTypes.put(Around.class,AspectJAnnotationType.AtAround);
   annotationTypes.put(Before.class,AspectJAnnotationType.AtBefore);
}

????通過核心點1,Spring已經(jīng)將注解@Before對應轉(zhuǎn)換為AtBefore召耘,@After轉(zhuǎn)換成AtAfter百炬,以此類推,都會一一映射到了核心點2的switch的條件類了污它,在核心點2中剖踊,會為對應的切面注解類生成Advice類。 所有的注解切面類具體實現(xiàn)都是由AbstractAspectJAdvice這個抽象類實現(xiàn)的衫贬,這個類的構(gòu)造函數(shù)有三個參數(shù):

//切面方法 這里可能是beforePrint
Method aspectJAroundAdviceMethod
//切入點表達式匹配器 這里指封裝了exression的匹配器
AspectJExpressionPointcut pointcut
//切面類  這里指TracesRecordAdvisor
AspectInstanceFactory aif

????下面是Spring為對應注解生成對應的Advice類

注解類 Advice 顧問方法
AtBefore AspectJMethodBeforeAdvice
AtAfter AspectJAfterAdvice
AtAfterReturning AspectJAfterReturningAdvice
AtAfterThrowing AspectJAfterThrowingAdvice
AtAround AspectJAroundAdvice

????各個注解會在不同的實際執(zhí)行自身增強方法德澈,這個部分只是生成Advice類,然會放入到緩存中固惯,等真正生成代理時就會調(diào)用這些方法梆造。這個在創(chuàng)建代理的時候需要具體拆開說,至此葬毫,Spring將使用了@Aspect注解的切面類的切面方法镇辉,都轉(zhuǎn)換成了對應的Adivsor類,這個類包含了切面方法贴捡,封裝后的切點匹配器PointCut以及生成切面類的實例對象忽肛,通過這個類就可以匹配到符合條件的目標類的目標方法,然后執(zhí)行增強操作了烂斋。

????由切面注解生成的Advice類屹逛,最終會放入到一個緩存中,當生成目標bean的時候汛骂,會將所有所以能夠匹配到目標bean的advice放入到集合中罕模,由一個實現(xiàn)了MethodInvocation的類統(tǒng)一管理調(diào)用過程,這個類后面會詳細說到帘瞭,這里簡單分析下AspectJAfterAdvice的invoke方法淑掌,看看它的調(diào)用過程

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        try {
             //調(diào)用是實現(xiàn)了MethodInvocation方法的類 這個其實是個鏈式調(diào)用
            return mi.proceed();
        }
        finally {
            //最終執(zhí)行后置增強方法
            invokeAdviceMethod(getJoinPointMatch(), null, null);
        }
    }

????上面的invoke方法需要一個MethodInvocation的參數(shù),上面的Advice類除了AspectJMethodBeforeAdvice之外蝶念,都實現(xiàn)了這個接口锋拖,所以可以實現(xiàn)鏈式調(diào)用,這個邏輯會在創(chuàng)建代理的具體講解祸轮,這里只是簡單分析下兽埃,這些adviceinvoke方法規(guī)定了切面方法于要增強方法的執(zhí)行時機。

11适袜、AOP代理初窺

????上面一部分操作主要是處理使用了@Aspect注解的切面類柄错,然后將切面類的所有切面方法根據(jù)使用的注解生成對應的Advisor的過程,這個Advisor包含了切面方法,切入點匹配器和切面類售貌,也就是準好了要增強的邏輯给猾,接下來就是要將這些邏輯注入到合適的位置進行增強,這部分的操作就是由老生常談的代理實現(xiàn)的了颂跨。

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
   if (bean != null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
       //如果要創(chuàng)建的類不是提前暴露的代理 則進入下面的方法
      if (!this.earlyProxyReferences.contains(cacheKey)) {
         return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}

????創(chuàng)建代理前敢伸,需要先校驗bean是否需要創(chuàng)建代理

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    //如果bean是通過TargetSource接口獲取 則直接返回
   if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
      return bean;
   }
    //如果bean是切面類 直接返回
   if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
   }
    //如果bean是Aspect 而且允許跳過創(chuàng)建代理, 加入advise緩存 返回
   if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
   }
   //如果前面生成的advisor緩存中存在能夠匹配到目標類方法的Advisor 則創(chuàng)建代理
   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;
}

????方法很簡單恒削,主要的關(guān)注點在getAdvicesAndAdvisorsForBeancreateProxy上池颈,第一個是獲取能夠匹配目標類方法的Advisor集合,如果這個集合不為空钓丰,則代表該類需要被增強躯砰,需要生成代理,如果匹配不到携丁,則表示該類并不需要被增強琢歇,無需創(chuàng)建代理。至于createProxy就很明顯了梦鉴,就是創(chuàng)建代理李茫,這個方法里面決定了使用jdk代理還是cglib代理,并且用到了前面生成的Advisor實現(xiàn)增強功能肥橙。 這部分內(nèi)容會放到下一篇文章中專門分析魄宏。

12、簡單總結(jié)

????簡單總結(jié)一下快骗,Spring AOP在第一階段完成的主要任務:

????讀取配置文件階段:

  • 讀取xml文件遇到 <aop:aspectj-autoproxy/>標簽時,找到命名空間處理器AopNamespaceHandler,然后找到處理該標簽的類AspectJAutoProxyBeanDefinitionParser

  • 通過AspectJAutoProxyBeanDefinitionParserparse方法塔次,將AspectJAwareAdvisorAutoProxyCreator注冊到容器的聲明周期中方篮。

????創(chuàng)建bean階段:

  • 執(zhí)行AspectJAwareAdvisorAutoProxyCreatorpostProcessBeforeInstantiation校驗目標類是否是Aspect類和AOP基礎類以及是否需要跳過不需要執(zhí)行代理的類

  • 獲取beanDefinitions中所有使用了Aspect注解的類,然后將切面方法根據(jù)使用的注解生成Advisor類放入到緩存(關(guān)鍵)

  • 調(diào)用AspectJAwareAdvisorAutoProxyCreatorpostProcessAfterInitialization的方法励负,對需要增強的類創(chuàng)建代理藕溅。

????這個就是Spring AOP在這個階段所完成的工作,下一部分將專門針對Spring如何實現(xiàn)jdk和cglib代理分析继榆。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(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
  • 文/不壞的土叔 我叫張陵冀惭,是天一觀的道長震叙。 經(jīng)常有香客問我,道長云头,這世上最難降的妖魔是什么捐友? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮溃槐,結(jié)果婚禮上匣砖,老公的妹妹穿的比我還像新娘。我一直安慰自己昏滴,他們只是感情好猴鲫,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谣殊,像睡著了一般拂共。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上姻几,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天宜狐,我揣著相機與錄音,去河邊找鬼蛇捌。 笑死抚恒,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的络拌。 我是一名探鬼主播俭驮,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼春贸!你這毒婦竟也來了混萝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤萍恕,失蹤者是張志新(化名)和其女友劉穎逸嘀,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體允粤,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡厘熟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年屯蹦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绳姨。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡登澜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出飘庄,到底是詐尸還是另有隱情脑蠕,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布跪削,位于F島的核電站谴仙,受9級特大地震影響,放射性物質(zhì)發(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

推薦閱讀更多精彩內(nèi)容

  • 概述 Spring是什么绩郎? Spring是一個開源框架,為了解決企業(yè)應用開發(fā)的復雜性而創(chuàng)建的翁逞,但是現(xiàn)在已經(jīng)不止于企...
    瑯筑閱讀 1,168評論 2 8
  • 轉(zhuǎn)自:https://javadoop.com/post/spring-aop-intro Spring AOP ...
    劍書藏于西閱讀 1,308評論 0 8
  • Spring AOP實現(xiàn) 概念簡介 Aspect:系統(tǒng)中挖函,功能模塊或者類的橫切面的一種抽象状植。舉例來說,web應用常...
    Sscon70閱讀 651評論 0 1
  • 本章內(nèi)容: 面向切面編程的基本原理 通過POJO創(chuàng)建切面 使用@AspectJ注解 為AspectJ切面注入依賴 ...
    謝隨安閱讀 3,149評論 0 9
  • 因為我的職業(yè)關(guān)系,所以常常會看到學生面對分離時的不舍與痛苦津畸,我也會一遍遍地經(jīng)歷與一屆屆學生分離的過程振定。每屆新高一的...
    一凡李子閱讀 525評論 3 5