<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一道經(jīng)典面試題:@Configuration 和 @Component 有何區(qū)別?

          共 19395字,需瀏覽 39分鐘

           ·

          2023-09-01 10:36

          關(guān)于 @Configuration 注解有一個特別經(jīng)典的面試題:

          • @Configuration 和 @Component 有什么區(qū)別?

          無論小伙伴們之前是否背過相關(guān)的面試題,今天這篇文章學(xué)完之后相信大家對這個問題都會有更深一層的理解,廢話不多少,咱們開始分析。

          1. 情景展現(xiàn)

          @Configuration 和 @Component 到底有何區(qū)別呢?我先通過如下一個案例,在不分析源碼的情況下,小伙伴們先來直觀感受一下這兩個之間的區(qū)別。

                
                @Configuration
          public class JavaConfig01 {
          }
          @Component
          public class JavaConfig02 {
          }

          首先,分別向 Spring 容器中注入兩個 Bean,JavaConfig01 和 JavaConfig02,其中,JavaConfig01 上添加的是 @Configuration 注解而 JavaConfig02 上添加的則是 @Component 注解。

          現(xiàn)在,在 XML 文件中配置包掃描:

                
                
                  <context:component-scan 
          base-package="org.javaboy.demo.p6"/>

          最后,加載 XML 配置文件,初始化容器:

                
                public class Demo {
              public static void main(String[] args) {
                  ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans_demo.xml");
                  JavaConfig01 config01 = ctx.getBean(JavaConfig01.class);
                  JavaConfig02 config02 = ctx.getBean(JavaConfig02.class);
                  System.out.println("config01.getClass() = " + config01.getClass());
                  System.out.println("config02.getClass() = " + config02.getClass());
              }
          }

          最終打印出來結(jié)果如下:

          7115df55ecd4470cdf2e299995b4b826.webp

          從上面這段代碼中,我們可以得出來兩個結(jié)論:

          1. @Configuration 注解也是 Spring 組件注解的一種,通過普通的 Bean 掃描也可以掃描到 @Configuration。
          2. @Configuration 注解注冊到 Spring 中的 Bean 是一個 CGLIB 代理的 Bean,而不是原始 Bean,這一點和 @Component 不一樣,@Component 注冊到 Spring 容器中的還是原始 Bean。

          一個問題來了,@Configuration 標記的類為什么注冊到 Spring 容器之后就變成了代理對象了呢?閉著眼睛大家也能猜到,肯定是為了通過代理來增強其功能,那么究竟增強什么功能呢?接下來我們通過源碼分析來和小伙伴們梳理一下這里的條條框框。

          2. 源碼分析

          要理解這個問題,首先得結(jié)合我們前面的文章@Configuration 注解的 Full 模式和 Lite 模式!,在該文中,松哥提到了 @Configuration 模式分為了 Full 模式和 Lite 模式,所以,對于 @Configuration 注解的處理,在加載的時候,就需要首先區(qū)分出來是 Full 模式還是 Lite 模式。

          負責(zé) @Configuration 注解的是 ConfigurationClassPostProcessor,這個處理器是一個 BeanFactoryPostProcessor,BeanFactoryPostProcessor 的作用就是在 Bean 定義的時候,通過修改 BeanDefinition 來重新定義 Bean 的行為,這個松哥之前有過專門的文章介紹,不熟悉的小伙伴可以先看看這里:

          同時,ConfigurationClassPostProcessor 也是 BeanDefinitionRegistryPostProcessor 的實例,BeanDefinitionRegistryPostProcessor 是干嘛的呢?

          BeanDefinitionRegistryPostProcessor 是 Spring 框架中的一個接口,它的作用是在應(yīng)用程序上下文啟動時,對 BeanDefinitionRegistry 進行后置處理。具體來說,BeanDefinitionRegistryPostProcessor 可以用于修改或擴展應(yīng)用程序上下文中的 BeanDefinition,即在 Bean 實例化之前對 BeanDefinition 進行修改。它可以添加、刪除或修改 BeanDefinition 的屬性,甚至可以動態(tài)地注冊新的 BeanDefinition。通過實現(xiàn) BeanDefinitionRegistryPostProcessor 接口,我們可以在 Spring 容器啟動過程中干預(yù) Bean 的定義,以滿足特定的需求。這使得我們可以在應(yīng)用程序上下文加載之前對 Bean 進行一些自定義的操作,例如動態(tài)注冊 Bean 或者修改 Bean 的屬性。需要注意的是,BeanDefinitionRegistryPostProcessor 在 BeanFactoryPostProcessor 之前被調(diào)用,因此它可以影響到 BeanFactoryPostProcessor 的行為。

          BeanFactoryPostProcessor 中的方法是 postProcessBeanFactory,而 BeanDefinitionRegistryPostProcessor 中的方法是 postProcessBeanDefinitionRegistry,根據(jù)前面的介紹,postProcessBeanDefinitionRegistry 方法將在 postProcessBeanFactory 方法之前執(zhí)行。

          所以,我們就從 postProcessBeanDefinitionRegistry 方法開始看起吧~

          2.1 postProcessBeanDefinitionRegistry

                
                @Override
          public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
           int registryId = System.identityHashCode(registry);
           if (this.registriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
              "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
           }
           if (this.factoriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
              "postProcessBeanFactory already called on this post-processor against " + registry);
           }
           this.registriesPostProcessed.add(registryId);
           processConfigBeanDefinitions(registry);
          }

          這個方面前面的代碼主要是為了確保該方法執(zhí)行一次,我們就不多說了。關(guān)鍵在于最后的 processConfigBeanDefinitions 方法,這個方法就是用來決策配置類是 Full 模式還是 Lite 模式的。

                
                public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
           List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
           String[] candidateNames = registry.getBeanDefinitionNames();
           for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
             if (logger.isDebugEnabled()) {
              logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
             }
            }
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
             configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
           }
           //省略。。。
          }

          我省略了其他代碼,大家看,這個方法中,會首先根據(jù) beanName 取出來 BeanDefinition,然后判斷 BeanDefinition 中是否包含 ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE 屬性,這個屬性上記錄了當前配置類是 Full 模式還是 Lite 模式,不同模式將來的處理方案肯定也是不同的。如果是第一次處理,顯然 BeanDefinition 中并不包含該屬性,因此就會進入到 ConfigurationClassUtils.checkConfigurationClassCandidate 方法中,正是在該方法中,判斷當前配置類是 Full 模式還是 Lite 模式,并進行標記,checkConfigurationClassCandidate 方法的邏輯也挺長的,我這里挑出來跟我們感興趣的部分:

                
                static boolean checkConfigurationClassCandidate(
            BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory)
           
          {
                  //省略。。。
           Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
           if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
           }
           else if (config != null || isConfigurationCandidate(metadata)) {
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
           }
           else {
            return false;
           }
              //省略
          }

          Full 模式情況很簡單,就是如果配置類上存在 @Configuration 注解,并且該注解的 proxyBeanMethods 屬性值不為 false,那么就是 Full 模式,這個跟松哥在 @Configuration 注解的 Full 模式和 Lite 模式!一文中的介紹是一致的。

          Lite 模式就情況多一些,首先 config!=null 就是說現(xiàn)在也存在 @Configuration 注解,但是 proxyBeanMethods 屬性值此時為 false,那么就是 Lite 模式(proxyBeanMethods 屬性值為 true 的話就進入到 if 分支中了)。

          另外就是在 isConfigurationCandidate 方法中有一些判斷邏輯去鎖定是否為 Lite 模式:

                
                static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
           // Do not consider an interface or an annotation...
           if (metadata.isInterface()) {
            return false;
           }
           // Any of the typical annotations found?
           for (String indicator : candidateIndicators) {
            if (metadata.isAnnotated(indicator)) {
             return true;
            }
           }
           // Finally, let's look for @Bean methods...
           return hasBeanMethods(metadata);
          }

          這個方法的判斷邏輯是這樣:

          1. 首先注解要是標記的是接口,那就不能算是 Lite 模式。
          2. 遍歷 candidateIndicators,判斷當前類上是否包含這個 Set 集合中的注解,這個 Set 集合中的注解有四個,分別是 @Component、@ComponentScan、@Import、@ImportResource 四個,也就是,如果類上標記的是這四個注解的話,那么也按照 Lite 模式處理。
          3. 判斷當前類中是否有 @Bean 標記的方法,如果有則按照 Lite 模式處理,否則就不是 Lite 模式。

          如果小伙伴們看過松哥之前的 @Configuration 注解的 Full 模式和 Lite 模式!一文,那么上面這些代碼應(yīng)該都很好理解,跟松哥在該文章中的介紹都是一致的。

          好了,經(jīng)過上面的處理,現(xiàn)在就已經(jīng)標 BeanDefinition 中標記了這個配置類到底是 Full 模式還是 Lite 模式了。

          2.2 postProcessBeanFactory

          接下來我們就來看 postProcessBeanFactory 方法。

                
                /**
           * Prepare the Configuration classes for servicing bean requests at runtime
           * by replacing them with CGLIB-enhanced subclasses.
           */

          @Override
          public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
           int factoryId = System.identityHashCode(beanFactory);
           if (this.factoriesPostProcessed.contains(factoryId)) {
            throw new IllegalStateException(
              "postProcessBeanFactory already called on this post-processor against " + beanFactory);
           }
           this.factoriesPostProcessed.add(factoryId);
           if (!this.registriesPostProcessed.contains(factoryId)) {
            // BeanDefinitionRegistryPostProcessor hook apparently not supported...
            // Simply call processConfigurationClasses lazily at this point then.
            processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
           }
           enhanceConfigurationClasses(beanFactory);
           beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
          }

          首先大家看一下這個方法的注釋,注釋說的很明確了,將 Configuration 類通過 CGLIB 進行增強,以便在運行時較好的處理 Bean 請求。

          這個方法中還會再次確認一下 postProcessBeanDefinitionRegistry 方法已經(jīng)處理過了,如果沒有處理的話,則會在該方法中調(diào)用 processConfigBeanDefinitions 去確認 Bean 使用的是哪種模式。

          該方法的關(guān)鍵在于 enhanceConfigurationClasses,這個就是用來通過動態(tài)代理增強配置類的,當然這個方法也是比較長的,我這里列出來一些關(guān)鍵的邏輯:

                
                public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
           StartupStep enhanceConfigClasses = this.applicationStartup.start("spring.context.config-classes.enhance");
           Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
           for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
            Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
            if ((configClassAttr != null || methodMetadata != null) &&
              (beanDef instanceof AbstractBeanDefinition abd) && !abd.hasBeanClass()) {
             // Configuration class (full or lite) or a configuration-derived @Bean method
             // -> eagerly resolve bean class at this point, unless it's a 'lite' configuration
             // or component class without @Bean methods.
             boolean liteConfigurationCandidateWithoutBeanMethods =
               (ConfigurationClassUtils.CONFIGURATION_CLASS_LITE.equals(configClassAttr) &&
                annotationMetadata != null && !ConfigurationClassUtils.hasBeanMethods(annotationMetadata));
             if (!liteConfigurationCandidateWithoutBeanMethods) {
              try {
               abd.resolveBeanClass(this.beanClassLoader);
              }
             }
            }
            if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
             configBeanDefs.put(beanName, abd);
            }
           }
           
           ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
           for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
            AbstractBeanDefinition beanDef = entry.getValue();
            // If a @Configuration class gets proxied, always proxy the target class
            beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
            // Set enhanced subclass of the user-specified bean class
            Class<?> configClass = beanDef.getBeanClass();
            Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
            if (configClass != enhancedClass) {
             beanDef.setBeanClass(enhancedClass);
            }
           }
           enhanceConfigClasses.tag("classCount", () -> String.valueOf(configBeanDefs.keySet().size())).end();
          }

          這個方法的邏輯,我整體上將之分為兩部分:

          第一部分就是先找到 Full 模式的配置類的名稱,存入到 configBeanDefs 集合中。

          具體尋找的邏輯就是根據(jù)配置類的模式去尋找,如果配置類是 Full 模式,就將之存入到 configBeanDefs 中。如果配置類是 Lite 模式,且里邊沒有 @Bean 標記的方法,那就說明這可能并不是一個配置類,就是一個普通 Bean,那么就在這里加載類就行了。

          第二步則是遍歷 configBeanDefs 集合,增強配置類。

          這個如果大家了解 CGLIB 動態(tài)代理的話,這個就很好懂了,關(guān)于 CGLIB 動態(tài)代理松哥這里不啰嗦,最近更新的 Spring 源碼視頻中都有詳細講到。那么這里主要是通過 enhancer.enhance 方法來生成代理類的,如下:

                
                public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
           if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
            return configClass;
           }
           Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
           return enhancedClass;
          }
          private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
           Enhancer enhancer = new Enhancer();
           enhancer.setSuperclass(configSuperClass);
           enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
           enhancer.setUseFactory(false);
           enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
           enhancer.setAttemptLoad(true);
           enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
           enhancer.setCallbackFilter(CALLBACK_FILTER);
           enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
           return enhancer;
          }

          小伙伴們看到,增強類中的 setCallbackFilter 是 CALLBACK_FILTER,這個里邊包含了幾個方法攔截器,跟我們相關(guān)的是 BeanMethodInterceptor,我們來看下:

                
                private static class BeanMethodInterceptor implements MethodInterceptorConditionalCallback {
           
           @Override
           @Nullable
           public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
              MethodProxy cglibMethodProxy)
           throws Throwable 
          {
            ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
            String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
            // Determine whether this bean is a scoped-proxy
            if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
             String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
             if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
              beanName = scopedBeanName;
             }
            }
            // To handle the case of an inter-bean method reference, we must explicitly check the
            // container for already cached instances.
            // First, check to see if the requested bean is a FactoryBean. If so, create a subclass
            // proxy that intercepts calls to getObject() and returns any cached bean instance.
            // This ensures that the semantics of calling a FactoryBean from within @Bean methods
            // is the same as that of referring to a FactoryBean within XML. See SPR-6602.
            if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
              factoryContainsBean(beanFactory, beanName)) {
             Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
             if (factoryBean instanceof ScopedProxyFactoryBean) {
              // Scoped proxy factory beans are a special case and should not be further proxied
             }
             else {
              // It is a candidate FactoryBean - go ahead with enhancement
              return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
             }
            }
            if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
             return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
            }
            return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
           }
          }

          自己寫過 CGLIB 動態(tài)代理的小伙伴都知道這里 intercept 方法的含義,這就是真正的攔截方法了,也就是說,如果我們的配置類是 Full 模式的話,那么將來調(diào)用 @Bean 注解標記的方法的時候,調(diào)用的其實是這里的 intercept 方法。

          上面方法,首先會判斷當前代理是否為作用域代理,我們這里當然不是。

          接下來判斷請求的 Bean 是否是一個 FactoryBean,如果是,則需要去代理其 getObject 方法,當執(zhí)行到 getObject 方法的時候,就去 Spring 容器中查找需要的 Bean,當然,我們這里也不屬于這種情況。

          接下來判斷當前正在執(zhí)行的方法,是否為容器中正在調(diào)用的工廠方法。

          例如我有如下代碼:

                
                @Configuration
          public class JavaConfig {

              @Bean
              User user() {
                  User user = new User();
                  user.setDog(dog());
                  return user;
              }

              @Bean
              Dog dog() {
                  return new Dog();
              }
          }

          那么如果是直接調(diào)用 dog() 方法,則 isCurrentlyInvokedFactoryMethod 返回 true,如果是在 user() 方法中調(diào)用的 dog() 方法,則 isCurrentlyInvokedFactoryMethod 返回 false。

          當 isCurrentlyInvokedFactoryMethod 返回 true 的時候,就執(zhí)行 invokeSuper 方法,也就是真正的觸發(fā) dog() 方法的執(zhí)行。

          當 isCurrentlyInvokedFactoryMethod 返回 false 的時候,則執(zhí)行下面的 resolveBeanReference 方法,這個方法會先去 Spring 容器中查找相應(yīng)的 Bean,如果 Spring 容器中不存在該 Bean,則會觸發(fā) Bean 的創(chuàng)建流程。

          現(xiàn)在,小伙伴們應(yīng)該明白了為什么 Full 模式下,調(diào)用 @Bean 注解標記的方法并不會導(dǎo)致 Bean 的重復(fù)創(chuàng)建了吧~

          好啦,本文結(jié)合上文 @Configuration 注解的 Full 模式和 Lite 模式! 一起食用效果更佳哦~


          推薦閱讀:

          刪除重復(fù)記錄但保留其中一行數(shù)據(jù)的sql寫法

          一條sql搞定這個需求,面試官直呼內(nèi)行

          MySQL如何進行表之間的關(guān)聯(lián)更新

          高頻面試題:多線程順序打印ABC字符20次

          一網(wǎng)打盡:MySQL索引失效的場景大搜羅

          這個設(shè)計模式的用法,一般人我不告訴他

          瀏覽 40
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人精品在线 | 国内精品国产三级国产在线专 | 午夜福利av电影 午夜福利电影AV 午夜精品福利在线 | 人人超人人超碰国产 | 在线观看免费观看在线黄色 |