你知道Spring是怎麼將AOP應用到Bean的生命周期中的嗎?

2021-02-14 程式設計師書單

在上篇文章中(Spring中AOP相關的API及源碼解析,原來AOP是這樣子的)我們已經分析過了AOP的實現的源碼,那麼Spring是如何將AOP應用到Bean的生命周期的呢?這篇文章就帶著大家來探究下這個問題。本文我們要分析的代碼還是位於org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean這個方法中,在《我們來談一談Spring中的屬性注入 》這篇文章中,我們已經分析過了populateBean這個方法,


所以本文我們接著來看看initializeBean這個方法,它主要幹了這麼幾件事

對應源碼如下:

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
  if (System.getSecurityManager() != null) {
   AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
    invokeAwareMethods(beanName, bean);
    return null;
   }, getAccessControlContext());
  }
  else {
           
            // 執行Aware接口中的方法
   invokeAwareMethods(beanName, bean);
  }

  Object wrappedBean = bean;
  if (mbd == null || !mbd.isSynthetic()) {
            
            // 調用InitDestroyAnnotationBeanPostProcessor
            // 的postProcessBeforeInitialization方法
            // 處理@PostContructor註解標註的方法
            // 另外有一部分aware方法也是在這裡調用的
   wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
  }

  try {
            // 如果實現了InitializingBean,會調用afterPropertiesSet方法
            // 如果XML中配置了init-method屬性,會調用對應的初始化方法
   invokeInitMethods(beanName, wrappedBean, mbd);
  }
  catch (Throwable ex) {
   throw new BeanCreationException(
     (mbd != null ? mbd.getResourceDescription() : null),
     beanName, "Invocation of init method failed", ex);
  }
  if (mbd == null || !mbd.isSynthetic()) {
            // 在這裡完成AOP
   wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
  }

  return wrappedBean;
 }

因為在Spring官網閱讀(九)Spring中Bean的生命周期(上)文章中我們已經對這個方法做過分析了,並且這個方法本身也比較簡單,所以不再對這個方法做過多贅述,我們主要關注的就是Spring是如何將AOP應用到Bean的生命周期中的,對應的就是applyBeanPostProcessorsAfterInitialization這個方法,其源碼如下:

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
    throws BeansException {

    Object result = existingBean;
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
        Object current = processor.postProcessAfterInitialization(result, beanName);
        if (current == null) {
            return result;
        }
        result = current;
    }
    return result;
}

實際上就是調用了所有後置處理器的postProcessAfterInitialization方法,在Spring中AOP相關的API及源碼解析,原來AOP是這樣子的一文中已經提到過了,@EnableAspectJAutoProxy註解實際上就是向容器中註冊了一個AnnotationAwareAspectJAutoProxyCreator,這個類本身就是一個後置處理器,AOP代理就是由它在這一步完成的。

Bean生命周期中AOP的流程1、@EnableAspectJAutoProxy

通過@EnableAspectJAutoProxy註解向容器中註冊一個AnnotationAwareAspectJAutoProxyCreator的BeanDefinition,它本身也是一個BeanPostProcessor,這個BeanDefinition會在org.springframework.context.support.AbstractApplicationContext#registerBeanPostProcessors這個方法中完成創建,如下圖所示


2、postProcessBeforeInstantiation方法執行

執行AnnotationAwareAspectJAutoProxyCreator的postProcessBeforeInstantiation方法,實際上就是父類AbstractAutoProxyCreator的postProcessBeforeInstantiation被執行

對應源碼如下:

//  這個方法的主要目的就是在不考慮通知的情況下,確認哪些Bean不需要被代理
//  1.Advice,Advisor,Pointcut類型的Bean不需要被代理
//  2.不是原始Bean被包裝過的Bean不需要被代理,例如ScopedProxyFactoryBean
//  實際上並不只是這些Bean不需要被代理,如果沒有對應的通知需要被應用到這個Bean上的話
//  這個Bean也是不需要被代理的,只不過不是在這個方法中處理的。
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
    Object cacheKey = getCacheKey(beanClass, beanName);
    // 如果beanName為空或者為這個bean提供了定製的targetSource
    if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
        // advisedBeans是一個map,其中key是BeanName,value代表了這個Bean是否需要被代理
        // 如果已經包含了這個key,不需要在進行判斷了,直接返回即可
        // 因為這個方法的目的就是在實例化前就確認哪些Bean是不需要進行AOP的
        if (this.advisedBeans.containsKey(cacheKey)) {
            return null;
        }
        // 說明還沒有對這個Bean進行處理
        // 在這裡會對SpringAOP中的基礎設施bean,例如Advice,Pointcut,Advisor做標記
        // 標誌它們不需要被代理,對應的就是將其放入到advisedBeans中,value設置為false
        // 其次,如果這個Bean不是最原始的Bean,那麼也不進行代理,也將其value設置為false
        if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return null;
        }
    }

    // 是否為這個Bean提供了定製的TargetSource
    // 如果提供了定製的TargetSource,那麼直接在這一步創建一個代理對象並返回
    // 一般不會提供
    TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
    if (targetSource != null) {
        if (StringUtils.hasLength(beanName)) {
            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;
}

3、postProcessAfterInitialization方法執行

實際上也是執行父類AbstractAutoProxyCreator中的方法,對應源碼如下:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 什麼時候這個判斷會成立呢?
        // 如果不出現循環引用的話,remove方法必定返回null
        // 所以這個remove(cacheKey) != bean肯定會成立
        // 如果發生循環依賴的話,這個判斷就不會成立
        // 這個我們在介紹循環依賴的時候再詳細分析,
        // 目前你只需要知道wrapIfNecessary完成了AOP代理
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            // 需要代理的話,在這裡完成的代理
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

4、wrapIfNecessary方法執行
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
   
    // 在postProcessBeforeInstantiation方法中可能已經完成過代理了
    // 如果已經完成代理了,那麼直接返回這個代理的對象
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    
    // 在postProcessBeforeInstantiation方法中可能已經將其標記為不需要代理了
    // 這種情況下,也直接返回這個Bean
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    
    // 跟在postProcessBeforeInstantiation方法中的邏輯一樣
    // 如果不需要代理,直接返回,同時在advisedBeans中標記成false
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // 獲取可以應用到這個Bean上的通知
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    // 如果存在通知的話,說明需要被代理
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        // 到這裡創建代理,實際上底層就是new了一個ProxyFactory來創建代理的
        Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }
 // 如果沒有通知的話,也將這個Bean標記為不需要代理
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

關於創建代理的具體源碼分析,在Spring中AOP相關的API及源碼解析,原來AOP是這樣子的一文中已經做了詳細介紹,所以本文不再贅述,現在我們的重點將放在Spring是如何解析出來通知的,對應方法就是getAdvicesAndAdvisorsForBean,其源碼如下:

第一步:調用org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean

protected Object[] getAdvicesAndAdvisorsForBean(
      Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
  
   // 通過findEligibleAdvisors方法返回對應的通知
   // 這個方法會返回所有能應用在指定的Bean上的通知
   List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
   
   if (advisors.isEmpty()) {
      return DO_NOT_PROXY;
   }
   return advisors.toArray();
}

第二步:調用org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findEligibleAdvisors

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    // 獲取到所有的通知
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // 從獲取到的通知中篩選出能應用到這個Bean上的通知
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

第三步:調用org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors獲取到所有的通知

// 這個方法的目的就是為了獲取到所有的通知
protected List<Advisor> findCandidateAdvisors() {
   
    // 先調用父類的方法,父類會去查找容器中所有屬於Advisor類型的Bean
    List<Advisor> advisors = super.findCandidateAdvisors();
    
   // 這個類本身會通過一個aspectJAdvisorsBuilder來構建通知
    // 構建的邏輯就是解析@Aspect註解所標註的類中的方法
    if (this.aspectJAdvisorsBuilder != null) {
        advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    }
    
    // 最後返回這些通知
    return advisors;
}

第四步:org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors構建通知,這個方法比較長,我們就只分析其中的關鍵代碼即可

public List<Advisor> buildAspectJAdvisors() {
  List<String> aspectNames = this.aspectBeanNames;

  if (aspectNames == null) {
   synchronized (this) {
    aspectNames = this.aspectBeanNames;
    if (aspectNames == null) {
     List<Advisor> advisors = new ArrayList<>();
     aspectNames = new ArrayList<>();
     // 會獲取到容器中的所有BeanName
     String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
       this.beanFactory, Object.class, true, false);
     for (String beanName : beanNames) {
      // 如果對beanName配置了正則匹配的話,那麼要按照正則表達式的匹配規則進行過濾
      // 默認是沒有的,可以認為isEligibleBean始終返回true
      if (!isEligibleBean(beanName)) {
       continue;
      }
      // We must be careful not to instantiate beans eagerly as in this case they
      // would be cached by the Spring container but would not have been weaved.
      Class<?> beanType = this.beanFactory.getType(beanName);
      if (beanType == null) {
       continue;
      }
      // 判斷類上是否添加了@Aspect註解
      if (this.advisorFactory.isAspect(beanType)) {
       aspectNames.add(beanName);
       AspectMetadata amd = new AspectMetadata(beanType, beanName);
       // 默認就是SINGLETON,代理切面對象是單例的
       if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                            // 最後從這個切面實例中解析出所有的通知
                            // 關於通知解析的具體代碼就不再分析了
         MetadataAwareAspectInstanceFactory factory =
          new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
        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);
       }
  // 省略部分代碼
  return advisors;
 }

第五步:org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findAdvisorsThatCanApply

protected List<Advisor> findAdvisorsThatCanApply(
    List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {

    ProxyCreationContext.setCurrentProxiedBeanName(beanName);
    try {
        return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
    }
    finally {
        ProxyCreationContext.setCurrentProxiedBeanName(null);
    }
}

這個方法其實沒啥好分析的,就是根據前面找出來的Advisor集合進行遍歷,然後根據每個Advisor對應的切點來進行匹配,如何合適就返回,對應源碼也比較簡單,當然前提是你看過我之前那篇AOP源碼分析的文章了.

總結

這篇文章比較短,因為沒有做很細節的源碼分析,比較詳細的源碼分析已經放到上篇文章中了。最後我這裡畫個流程圖總結一下AOP是怎麼被應用到Bean的生命周期中的


Spring源碼的最後一點補充
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    throws BeanCreationException {
    // 1.實例化    ---> createBeanInstance
    // 2.屬性注入  ---> populateBean
    // 3.初始化    ---> 完成初始化及AOP
    // exposedObject 就是完成初始化後的Bean  
    // 省略部分代碼,省略代碼的作用已經在上面標明了
    
    // 下面的代碼實際上主要目的在於處理循環依賴
    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
            // 我們之前早期暴露出去的Bean跟現在最後要放到容器中的Bean不是同一個
            // allowRawInjectionDespiteWrapping為false
            // 並且當前Bean被當成依賴注入到了別的Bean中
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                // 獲取到當前Bean所從屬的Bean
                String[] dependentBeans = getDependentBeans(beanName);
                // 要得到真實的從屬的Bean
                Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                for (String dependentBean : dependentBeans) {
                    // 移除那些僅僅為了類型檢查而創建出來
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                if (!actualDependentBeans.isEmpty()) {
     // 拋出異常
                    // 出現了循環依賴,並且實際存在容器中的Bean跟被當作依賴注入到別的Bean中的
                    // 不是同一個對象,這個時候也報錯
                }
            }
        }
    }

    // 註冊bean的銷毀回調
    try {
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    }

    return exposedObject;
}

實際這段代碼還是跟循環依賴相關,關於循環依賴的文章,大家可以參考:面試必殺技,講一講Spring中的循環依賴

如果我的文章能幫到你,記得點個讚哈~!

我叫DMZ,一個在學習路上匍匐前行的小菜鳥!

本公眾號全部博文已整理成一個目錄,請在公眾號裡回復「m」獲取!

3T技術資源大放送!包括但不限於:Java、C/C++,Linux,Python,大數據,人工智慧等等。在公眾號內回復「1024」,即可免費獲取!!


相關焦點

  • 深究Spring中Bean的生命周期
    網上大部分都是驗證的Bean 在面試問的生命周期,其實查閱JDK還有一個完整的Bean生命周期,這同時也驗證了書是具有片面性的,最fresh 的資料還是查閱原始JDK!!!一、Bean 的完整生命周期在傳統的Java應用中,bean的生命周期很簡單,使用Java關鍵字 new 進行Bean 的實例化,然後該Bean 就能夠使用了。
  • 再談Spring中Bean的生命周期
    一、Bean 的完整生命周期在傳統的Java應用中,bean的生命周期很簡單,使用Java關鍵字 new 進行Bean 的實例化,然後該Bean 就能夠使用了。一旦bean不再被使用,則由Java自動進行垃圾回收。
  • Spring框架IOC和AOP簡介
    >◎包含並管理應用對象的配置和生命周期,這個意義上是一種容器◎將簡單的組件配置、組合成為複雜的應用,這個意義上是框架3、Spring作用◎容器◎提供了對多種技術的支持:JMS、MQ、UnitTest等◎request:每次http請求創建一個實例且在當前request內有效◎session: 同上,當前session有效◎global session:基於portlet的web中有效,如果是在web中,同session(2)Bean的生命周期
  • Spring中Bean的單例和多例簡單總結
    在Spring中,有兩個類型,一個是單例一個和多例,一個Bean同學,要麼是單例要麼是多例。不管你認不認這兩個類型,它們依然在哪裡,不吵不鬧。想要和Bean同學成為哥們,那這兩個類型務必要知道。此外,singleton類型的bean定義從容器啟動到第一次被請求而實例化開始,只要容器不銷毀或退出,該類型的bean的單一實例就會一直存活,典型單例模式,如同servlet在web容器中的生命周期。prototype(多例):對這個bean的每次請求都會創建一個新的bean實例,類似於new。
  • 如何記憶 Spring Bean 的生命周期
    引言「請你描述下 Spring Bean 的生命周期?」,這是面試官考察 Spring 的常用問題,可見是 Spring 中很重要的知識點。我之前在準備面試時,去網上搜過答案,大多以下圖給出的流程作為答案。
  • Spring5.0源碼學習系列之Spring AOP簡述
    2、AOP的本質目的AOP本質:在不改變原有業務邏輯的情況下增強橫切邏輯,這個橫切邏輯可以是權限校驗邏輯、日誌監控、事務控制等等AOP相關知識詳情可以參考:Spring AOP官方文檔3、AOP的相關術語名詞描述連接點(Joinpoint)連接點是一個程序的執行,如方法的執行或異常的處理過程中的一個點切入點(Pointcut)指的是將增強代碼織入到業務主線進來之後的連接點通知
  • Java 第一大框架:Spring 的 IoC 跟 AOP 雛形如何實現?
    從簡單性、可測試性和鬆耦合性角度而言,絕大部分Java應用都可以從Spring中受益。目的:解決企業應用開發的複雜性,簡化應用程式的開發。功能:使用基本的JavaBean代替EJB,並提供了更多的企業應用功能範圍:任何Java應用 核心點:Spring是一個輕量級控制反轉(==IoC==)和面向切面(==AOP==)的容器框架。
  • spring AOP是什麼?你都拿它做什麼?
    在網際網路時代,無論你是牛人大咖,還是小白菜鳥,都有發表自己看法的權利。無論你是對的還是錯的,都會在這個平臺上找到答案。所以,我會儘可能去寫自己感興趣的內容,無論正面或者負面的消息,都儘可能回復我的每一位讀者。即使自己只有一個讀者,也會堅持寫下去。有一個平臺,去表達自己,記錄自己的點滴,難道不是一種快樂嗎?同樣,了解技術是一個深入和拓展的過程,需要一個人清晰嚴謹的邏輯思維。
  • Spring AOP是什麼?你都拿它做什麼?
    為了方便閱讀,博主把單獨的 class 文件合併到接口中,讀者可以直接複製代碼運行:package test.staticProxy; public interface IUserDao {    void save();    void find();} class UserDao implements IUserDao
  • 求求你,下次面試別再問我什麼是AOP了!
    批量的方式使用方法proxyFactoryBean.setInterceptorNames("需要匹配的bean名稱*");需要匹配的bean名稱後面跟上一個*,可以用來批量的匹配,如:interceptor*,此時spring會從容器中找到下面2中類型的所有bean,bean名稱以interceptor開頭的將作為增強器org.springframework.aop.Advisor
  • 如何理解 Spring AOP 以及使用 AspectJ?
    那麼,在傳統的業務處理代碼中,比如你要操作資料庫,會進行事務的處理或者列印一些日誌。雖然通過OOP 也可以實現,比如通過繼承或組合的方式來達到代碼的復用,但是如果實現某些功能,比如日誌記錄,相同的代碼會分散到各個方法中,如果後面要想關閉某個功能或進行修改就必須要修改所有的方法,非常的不方便。
  • 搞Java的你還不會Spring?一篇文章帶你掌握
    ><constructor-arg value="300000"></constructor-arg></bean>若是這樣配置,Spring將按照順序將屬性值依次注入到Bean中,你也可以指定注入屬性值的先後順序。
  • spring微服務中的那點事
    Inversion of Control 和 Dependency Injection 的基本思想就是把類的依賴從類內部轉化到外部以減少依賴。 應用Inversion of Control,對象在被創建的時候,由一個調控系統內所有對象的外界實體,將其所依賴的對象的引用,傳遞給它。也可以說,依賴被注入到對象中。
  • 詳解設計模式在 Spring 中的應用
    value> </constructor-arg> </bean> </beans>第二種:工廠方法(Factory Method)通常由應用程式直接使用new創建新的對象,為了將對象的創建和使用相分離,採用工廠模式,即應用程式將對象的創建及初始化職責交給工廠對象
  • 詳解Spring框架的AOP機制
    AOP是Spring框架面向切面的編程思想,AOP採用一種稱為「橫切」的技術,將涉及多業務流程的通用功能抽取並單獨封裝,形成獨立的切面,在合適的時機將這些切面橫向切入到業務流程指定的位置中。本篇結合實際案例詳細講述AOP的原理及實現過程。通過本篇的學習,可以達成如下目標。
  • Spring事務管理
    SavePoint保存點事務中可以設置一些保存點(階段標識),回滾時可以指定回滾到前面的哪個保存點。6. Commit/Rollback提交/回滾提交、回滾事務三、Spring事務使用學習1. 配置事務管理器 <!
  • 你知道Spring是怎麼解析配置類的嗎?
    正常情況下Spring都是採用掃描classpath下的class文件來完成掃描,但是雖然基於classpath掃描速度非常快,但通過在編譯時創建候選靜態列表,可以提高大型應用程式的啟動性能。在這種模式下,應用程式的所有模塊都必須使用這種機制,因為當 ApplicationContext檢測到這樣的索引時,它將自動使用它而不是掃描類路徑。
  • Spring事務基礎
    」,選擇「設為星標」前言我猜大概50%的Java程式設計師(別問我怎麼知道的涉及到調用第三方接口,請求第三方接口成功但返回相關交易失敗的話,需要刪除插入表的某條數據,或更新別表中的表狀態同時記錄日誌等,將第三方請求的實際完成情況返回給前端。
  • 11張圖:幫你搞定 Spring Bean 生命周期
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫來源:www.jianshu.com/p/70b935f2b3fe在網上已經有跟多Bean的生命周期的博客,但是很多都是基於比較老的版本了
  • SpringFramework學習筆記
    Metadata)BeanFacotry 是 Spring IoC 容器ApplicationContext 是具備應用特性的 BeanFactory 超集6.Spring Ioc 生命周期7.面試題什麼是 Spring IoC 容器?