Spring AOP 注解配置 源碼學(xué)習(xí)

上篇Spring AOP學(xué)習(xí)中已經(jīng)基本介紹了AOP是如何使用的,本文章來(lái)說(shuō)說(shuō)AOP注解方法的源碼細(xì)節(jié)
先提幾個(gè)問(wèn)題,在接下來(lái)的源碼學(xué)習(xí)中發(fā)現(xiàn)答案

  • <aop:aspectj-autoproxy />的工作原理是什么搬卒,能帶來(lái)什么作用
  • @Aspect是在什么時(shí)候被操作的
  • 各個(gè)切入點(diǎn)是按照什么樣的順序執(zhí)行
  • 切入點(diǎn)是如何和對(duì)應(yīng)的bean綁定在一起的

具體的demo可以看Spring AOP學(xué)習(xí)#注解

XML配置解析

AOP的配置最簡(jiǎn)單的方法就是在xml中加入<aop:aspectj-autoproxy />瑟俭,應(yīng)該很清楚spring是選擇什么命名空間去解析了,直接定位到AopNamespaceHandler 類契邀,而且定位的是AspectJAutoProxyBeanDefinitionParser解析器摆寄,如果這點(diǎn)有什么疑問(wèn)的話,最好是再看看spring xml的bean提取 源碼學(xué)習(xí),看完就懂了為什么會(huì)直接定位到該解析器

public class AopNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        // In 2.0 XSD as well as in 2.1 XSD.
        registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        // AspectJAutoProxyBeanDefinitionParser 是我們這次關(guān)注的類
        registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }
}

AspectJAutoProxyBeanDefinitionParser 類

class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser {
    
    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
      // 可以生成AnnotationAwareAspectJAutoProxyCreator類的beandefinition
      // 并注冊(cè)到IOC容器中
        extendBeanDefinition(element, parserContext);
        // 擴(kuò)展該bean的屬性信息
        return null;
    }

    private void extendBeanDefinition(Element element, ParserContext parserContext) {
        BeanDefinition beanDef =
                parserContext.getRegistry().getBeanDefinition(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
      // 該bean其實(shí)就是上面所說(shuō)的AnnotationAwareAspectJAutoProxyCreator的beandefinition
        if (element.hasChildNodes()) {
            addIncludePatterns(element, parserContext, beanDef);
            // 如果包含了子節(jié)點(diǎn)可以添加額外的includePattern
        }
    }

    private void addIncludePatterns(Element element, ParserContext parserContext, BeanDefinition beanDef) {
        ManagedList<TypedStringValue> includePatterns = new ManagedList<TypedStringValue>();
        NodeList childNodes = element.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node node = childNodes.item(i);
            if (node instanceof Element) {
                Element includeElement = (Element) node;
                TypedStringValue valueHolder = new TypedStringValue(includeElement.getAttribute("name"));
                // 獲取子屬性key為name的值
                valueHolder.setSource(parserContext.extractSource(includeElement));
                includePatterns.add(valueHolder);
            }
        }
        if (!includePatterns.isEmpty()) {
            includePatterns.setSource(parserContext.extractSource(element));
            beanDef.getPropertyValues().add("includePatterns", includePatterns);
            // 給該beandefinition賦屬性值坯门,includePatterns
        }
    }
}

如上述代碼中的AnnotationAwareAspectJAutoProxyCreator類創(chuàng)建中微饥,如果跳進(jìn)去能夠看到還可以為該類設(shè)置proxy-target-classexpose-proxy兩個(gè)boolean屬性值。
也可以設(shè)置子屬性值name,例如下面的設(shè)置

<aop:aspectj-autoproxy proxy-target-class="false" expose-proxy="true">
   <aop:include name="******" />
</aop:aspectj-autoproxy>

這樣就清楚了這個(gè)xml配置的原理是如何田盈,其實(shí)就是新添加了一個(gè)beandefinition而已畜号。

Aspect 掃描和實(shí)例化

spring正常的注冊(cè)的bean缴阎,如果沒(méi)有加@Aspect注解也是沒(méi)有用的允瞧,那么就來(lái)學(xué)習(xí)下是在什么時(shí)候被掃描到,以及后續(xù)的操作是什么蛮拔。

如果大概看了AnnotationAwareAspectJAutoProxyCreator類的相關(guān)內(nèi)容述暂,可以知道AOP所有的aspect以及pointCut都存儲(chǔ)在該對(duì)象中,所以掃描所有的注解@Aspect類建炫,肯定是在AnnotationAwareAspectJAutoProxyCreator類的實(shí)例化之后畦韭!

在refresh()代碼中,在實(shí)例化具體的bean之前肛跌,已經(jīng)完成了spring層面的bean實(shí)例


image.png

在接下來(lái)對(duì)org.springframework.context.event.internalEventListenerProcessor對(duì)象實(shí)例化的過(guò)程中會(huì)完成對(duì)@aspect類的掃描操作艺配,并把相關(guān)信息注入到上圖圈住的AnnotationAwareAspectJAutoProxyCreator類中。

隨著代碼調(diào)試到AbstractAutoProxyCreator文件

AbstractAutoProxyCreator 類

public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {  
   Object cacheKey = getCacheKey(beanClass, beanName);
   // 從當(dāng)前的cache容器獲取到bean的name
    if (beanName == null || !this.targetSourcedBeans.contains(beanName)) {
        if (this.advisedBeans.containsKey(cacheKey)) {
            return null;
            // 該advisedBeans已經(jīng)包含了該key衍慎,則直接退出不考慮
        }
        if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
           // 該bean不符合所有獲取到的切面转唉,設(shè)置為false
           // shouldSkip方法看下面的代碼
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return null;
        }
    }

   // 所有的注解信息已經(jīng)獲取到了
    // 具體通過(guò)代理類生成一個(gè)對(duì)象信息
    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);
            // 利用ProxyFactory 生成一個(gè)新的對(duì)象
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }
    }

    return null;
}

AspectJAwareAdvisorAutoProxyCreator 類

protected boolean shouldSkip(Class<?> beanClass, String beanName) {
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // 獲取所有的advisor,也就是PointCut稳捆,具體細(xì)節(jié)可看下面的函數(shù)執(zhí)行過(guò)程代碼
    for (Advisor advisor : candidateAdvisors) {
        if (advisor instanceof AspectJPointcutAdvisor) {
            if (((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) {
                return true;
            }
        }
    }
    return super.shouldSkip(beanClass, beanName);
}

BeanFactoryAdvisorRetrievalHelper 類

這個(gè)類是AnnotationAwareAspectJAutoProxyCreator的一個(gè)對(duì)象

    public List<Advisor> findAdvisorBeans() {
        String[] advisorNames = null;
        synchronized (this) {
            advisorNames = this.cachedAdvisorBeanNames;
            if (advisorNames == null) {
              // 還沒(méi)準(zhǔn)備好赠法,首次進(jìn)入該函數(shù)時(shí),為null
                advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                        this.beanFactory, Advisor.class, true, false);
               // 獲取到IOC容器中乔夯,所有類的類型是Advisor.class的
               // 當(dāng)然就我們當(dāng)前的demo而言肯定是沒(méi)有的砖织,只有加上了Advisor.class注解的類
               // 會(huì)繼續(xù)進(jìn)入到**buildAspectJAdvisors函數(shù)**中去獲取到注解為Advisor.class的類
                this.cachedAdvisorBeanNames = advisorNames;
            }
        }
        if (advisorNames.length == 0) {
            return new LinkedList<Advisor>();
        }

        List<Advisor> advisors = new LinkedList<Advisor>();
        for (String name : advisorNames) {
            if (isEligibleBean(name)) {
                if (this.beanFactory.isCurrentlyInCreation(name)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Skipping currently created advisor '" + name + "'");
                    }
                }
                else {
                    try {
                        advisors.add(this.beanFactory.getBean(name, Advisor.class));
                    }
                    catch (BeanCreationException ex) {
                        Throwable rootCause = ex.getMostSpecificCause();
                        if (rootCause instanceof BeanCurrentlyInCreationException) {
                            BeanCreationException bce = (BeanCreationException) rootCause;
                            if (this.beanFactory.isCurrentlyInCreation(bce.getBeanName())) {
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Skipping advisor '" + name +
                                            "' with dependency on currently created bean: " + ex.getMessage());
                                }
                                // Ignore: indicates a reference back to the bean we're trying to advise.
                                // We want to find advisors other than the currently created bean itself.
                                continue;
                            }
                        }
                        throw ex;
                    }
                }
            }
        }
        return advisors;
    }
public List<Advisor> buildAspectJAdvisors() {
    List<String> aspectNames = this.aspectBeanNames;

    if (aspectNames == null) {
       // 第一次進(jìn)入款侵,確實(shí)為null
        synchronized (this) {
            aspectNames = this.aspectBeanNames;
            // spring中為了確保線程安全,存在大量的類似double-check的代碼
            if (aspectNames == null) {
                List<Advisor> advisors = new LinkedList<Advisor>();
                aspectNames = new LinkedList<String>();
                String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                        this.beanFactory, Object.class, true, false);
                // 獲取IOC所有類型為Object的類名稱(肯定包含了注解@Aspect的相關(guān)類)
                for (String beanName : beanNames) {
                    if (!isEligibleBean(beanName)) {
                       // 類名和includePatterns正則匹配不通過(guò)侧纯,則跳過(guò)新锈,起到一個(gè)過(guò)濾的作用
                       // 匹配的是類似于com.demo.*這樣的類名稱
                       // 和上面說(shuō)的<aop:include name="******" />相關(guān)聯(lián)
                        continue;
                    }
                    
                    Class<?> beanType = this.beanFactory.getType(beanName);
                    if (beanType == null) {
                       // 獲取其類型,如果為null眶熬,無(wú)效壕鹉,跳過(guò)
                        continue;
                    }
                    if (this.advisorFactory.isAspect(beanType)) {
                       // 注解包含了@Aspect 并且沒(méi)有被AspectJ編譯(字段以ajc$開(kāi)頭)的類
                        aspectNames.add(beanName);
                        // 本demo的beanName就是animalAop
                        AspectMetadata amd = new AspectMetadata(beanType, beanName);
                        if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                            MetadataAwareAspectInstanceFactory factory =
                                    new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                            List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                            // 獲取該類所有的方法烈炭,例如@Before抬闯、@After等
                            // 取到該類所有的方法,按照Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class 的順序排序
                            // 依次遍歷榜田,如果發(fā)現(xiàn)了上述的注解牍白,則保存pointCut到一個(gè)容器中
                            
                            // 獲取該類所有的共有方法脊凰,如果符合要求,也保存到容器中茂腥,最后返回到List中
                            
                            if (this.beanFactory.isSingleton(beanName)) {
                                this.advisorsCache.put(beanName, classAdvisors);
                                // 如果是單例狸涌,則保存到cache共,否則保存到工廠cache中
                            }
                            else {
                                this.aspectFactoryCache.put(beanName, factory);
                            }
                            advisors.addAll(classAdvisors);
                        }
                        else {
                            // Per target or per this.
                            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));
                        }
                    }
                }
                // 這里給aspectNames 賦值
                // 需要注意到一旦賦值了最岗,就不會(huì)為空帕胆,則不會(huì)再去掃描所有的bean,得到全部的AOP信息
                this.aspectBeanNames = aspectNames;
                return advisors;
            }
        }
    }
    
    // 能運(yùn)行到這里的肯定是第二次執(zhí)行般渡,獲取到需要的AOP的pointCut信息
    if (aspectNames.isEmpty()) {
        return Collections.emptyList();
    }
    List<Advisor> advisors = new LinkedList<Advisor>();
    for (String aspectName : aspectNames) {
        List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
        if (cachedAdvisors != null) {
            advisors.addAll(cachedAdvisors);
        }
        else {
            MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
            advisors.addAll(this.advisorFactory.getAdvisors(factory));
        }
    }
    return advisors;
}

最后實(shí)例化代理對(duì)象AnimalImpl對(duì)象懒豹,經(jīng)過(guò)調(diào)試來(lái)到了


image.png

AbstractAutoProxyCreator 文件

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // Create proxy if we have advice.
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    // 得到相關(guān)的advice
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        Object proxy = createProxy(
                bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        // 這一步就是最關(guān)鍵的步驟,最后生成代理類
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

如下圖驯用,調(diào)試中生成了最終的代理類


image.png

至此脸秽,整個(gè)的代理類的實(shí)現(xiàn)過(guò)程就全部完成了。

總結(jié)下整個(gè)AOP注解操作的過(guò)程

1蝴乔、先利用xml配置记餐,添加AnnotationAwareAspectJAutoProxyCreator類到IOC容器中
2、在實(shí)例化各種beanpostprocessor的時(shí)候薇正,掃描IOC所有的bean片酝,如果配置了includePattern屬性,還需要對(duì)IOC容器的所有bean進(jìn)行正則匹配挖腰,通過(guò)的才會(huì)進(jìn)行接下來(lái)的操作
3雕沿、所有合適的bean遍歷找到注解為@Aspect的類,輪詢bean的方法曙聂,如果包含了Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class的注解方法晦炊,也需要收集起來(lái),最后形成kv對(duì),添加到AnnotationAwareAspectJAutoProxyCreator的cache容器中
4断国、實(shí)例化被代理的類之前需要實(shí)例化本身
5贤姆、從AnnotationAwareAspectJAutoProxyCreator存儲(chǔ)的所有advisor匹配出符合規(guī)則的advisor

具體可看AopUtils抽象類的findAdvisorsThatCanApply 方法

6、最后生成一個(gè)ProxyFactory稳衬,添加上述得到的各種數(shù)據(jù)霞捡,生成代理類

再來(lái)回答文章前提到的幾個(gè)問(wèn)題

  • <aop:aspectj-autoproxy />的工作原理是什么,能帶來(lái)什么作用

無(wú)需再?gòu)?qiáng)調(diào)薄疚,如果還是無(wú)法理解碧信,可以仔細(xì)閱讀spring xml的bean提取 源碼學(xué)習(xí),帶來(lái)的作用就是新增了一個(gè)管理Aspect的類,所有的advisor都是存放在這個(gè)類中

  • @Aspect是在什么時(shí)候被操作的

beanpostprocessor實(shí)例化的時(shí)候街夭,特指org.springframework.context.event.internalEventListenerProcessor類的實(shí)例化時(shí)砰碴,會(huì)去掃描所有的bean,檢查是否存在@Aspect注解板丽,如果有該注解呈枉,則添加到AnnotationAwareAspectJAutoProxyCreator中

  • 各個(gè)切入點(diǎn)是按照什么樣的順序執(zhí)行

切入點(diǎn)是按照Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class自定義的排序規(guī)則sort的操作的

  • 切入點(diǎn)是如何和對(duì)應(yīng)的bean綁定在一起的

相互獨(dú)立的,只有在bean具體生成的時(shí)候從AnnotationAwareAspectJAutoProxyCreator中獲取到合適的切面以及切點(diǎn)信息埃碱,最后生成代理類猖辫。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市砚殿,隨后出現(xiàn)的幾起案子啃憎,更是在濱河造成了極大的恐慌,老刑警劉巖似炎,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辛萍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡名党,警方通過(guò)查閱死者的電腦和手機(jī)叹阔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)挠轴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)传睹,“玉大人,你說(shuō)我怎么就攤上這事岸晦∨菲。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵启上,是天一觀的道長(zhǎng)邢隧。 經(jīng)常有香客問(wèn)我,道長(zhǎng)冈在,這世上最難降的妖魔是什么倒慧? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上纫谅,老公的妹妹穿的比我還像新娘炫贤。我一直安慰自己,他們只是感情好付秕,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布兰珍。 她就那樣靜靜地躺著,像睡著了一般询吴。 火紅的嫁衣襯著肌膚如雪掠河。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,785評(píng)論 1 314
  • 那天猛计,我揣著相機(jī)與錄音唠摹,去河邊找鬼。 笑死奉瘤,一個(gè)胖子當(dāng)著我的面吹牛跃闹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播毛好,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼望艺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了肌访?” 一聲冷哼從身側(cè)響起找默,我...
    開(kāi)封第一講書(shū)人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吼驶,沒(méi)想到半個(gè)月后惩激,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蟹演,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年风钻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酒请。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡骡技,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出羞反,到底是詐尸還是另有隱情布朦,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布昼窗,位于F島的核電站是趴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏澄惊。R本人自食惡果不足惜唆途,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一富雅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肛搬,春花似錦吹榴、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至让腹,卻和暖如春远剩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背骇窍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工瓜晤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人腹纳。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓痢掠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親嘲恍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子足画,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)佃牛,斷路器淹辞,智...
    卡卡羅2017閱讀 134,717評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,867評(píng)論 6 342
  • 上篇我們介紹了Spring中有關(guān)高級(jí)依賴關(guān)系配置的內(nèi)容,也可以調(diào)用任意方法的返回值作為屬性注入的值俘侠,它解決了Spr...
    Single_YAM閱讀 613評(píng)論 0 6
  • 文章作者:Tyan博客:noahsnail.com 3.4 Dependencies A typical ente...
    SnailTyan閱讀 4,174評(píng)論 2 7
  • 很久沒(méi)去伺弄樓下那塊地了象缀,久到都快忘了它的存在。今天從陽(yáng)臺(tái)看下去爷速,唯有我的一小塊還有些蔥郁央星,其他人的早已是枯草黃...
    舊衣架與光閱讀 289評(píng)論 0 0