欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring中@Configuration和@Component注解的區(qū)別及原理

 更新時間:2023年11月28日 09:56:07   作者:搬山道猿  
這篇文章主要介紹了Spring中@Configuration和@Component注解的區(qū)別及原理,從功能上來講,這些注解所負責(zé)的功能的確不相同,但是從本質(zhì)上來講,Spring內(nèi)部都將其作為配置注解進行處理,需要的朋友可以參考下

1.背景

隨著Spring Boot的盛行,注解配置式開發(fā)受到了大家的青睞,從此告別了基于Spring開發(fā)的繁瑣XML配置。

這里先來提綱挈領(lǐng)的了解一下Spring內(nèi)部對于配置注解的定義,如@Component、@Configuration、@Bean、@Import等注解

從功能上來講,這些注解所負責(zé)的功能的確不相同,但是從本質(zhì)上來講,Spring內(nèi)部都將其作為配置注解進行處理。

對于一個成熟的框架來講,簡單及多樣化的配置是至關(guān)重要的,那么Spring也是如此,從Spring的配置發(fā)展過程來看,整體的配置方式從最初比較“原始”的階段到現(xiàn)在非常“智能”的階段,這期間Spring做出的努力是非常巨大的,從XML到自動裝配,從Spring到Spring Boot,從@Component到@Configuration以及@Conditional,Spring發(fā)展到今日,在越來越好用的同時,也為我們隱藏了諸多的細節(jié),那么今天讓我們一起探秘@Component與@Configuration。

我們平時在Spring的開發(fā)工作中,基本都會使用配置注解,尤其以@Component及@Configuration為主,當然在Spring中還可以使用其他的注解來標注一個類為配置類,這是廣義上的配置類概念,但是這里我們只討論@Component和@Configuration,因為與我們的開發(fā)工作關(guān)聯(lián)比較緊密,那么接下來我們先討論下一個問題,就是 @Component與@Configuration有什么區(qū)別?

2.@Component與@Configuration使用

2.1注解定義

在討論兩者區(qū)別之前,先來看看兩個注解的定義:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
?
  /**
   * The value may indicate a suggestion for a logical component name,
   * to be turned into a Spring bean in case of an autodetected component.
   * @return the suggested component name, if any (or empty String otherwise)
   */
  String value() default "";
?
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
?
  @AliasFor(annotation = Component.class)
  String value() default "";
?
  boolean proxyBeanMethods() default true;
?
}

從定義來看, @Configuration 注解本質(zhì)上還是 @Component,因此 @ComponentScan 能掃描到@Configuration 注解的類。

2.2注解使用

接下來看看兩者在我們?nèi)粘i_發(fā)中的使用:以這兩種注解來標注一個類為配置類

@Configuration
public class AppConfig {
}
?
@Component
public class AppConfig {
}

上面的程序,Spring會將其認為配置類來做處理,但是這里有一個概念需要明確一下,就是在Spring中,對于配置類來講,其實是有分類的,大體可以分為兩類,一類稱為LITE模式,另一類稱為FULL模式,那么對應(yīng)上面的注解,@Component就是LITE類型,@Configuration就是FULL類型,如何理解這兩種配置類型呢?我們先來看這個程序。

當我們使用@Component實現(xiàn)配置類時:

@Component
public class AppConfig {
  @Bean
  public Foo foo() {
    System.out.println("foo() invoked...");
    Foo foo = new Foo();
    System.out.println("foo() 方法的 foo hashcode: " + foo.hashCode());
    return foo;
  }
?
  @Bean
  public Eoo eoo() {
    System.out.println("eoo() invoked...");
    Foo foo = foo();
    System.out.println("eoo() 方法的 foo hashcode: "+ foo.hashCode());
    return new Eoo();
  }
?
  public static void main(String[] args) {
    AnnotationConfigApplicationContext context =
        new AnnotationConfigApplicationContext(AppConfig.class);
  }
}

執(zhí)行結(jié)果如下:

foo() invoked...
foo() 方法的 foo hashcode: 815992954
eoo() invoked...
foo() invoked...
foo() 方法的 foo hashcode: 868737467
eoo() 方法的 foo hashcode: 868737467

從結(jié)果可知,foo()方法執(zhí)行了兩次,一次是bean方法執(zhí)行的,一次是eoo()調(diào)用執(zhí)行的,所以兩次生成的foo對象是不一樣的。很符合大家的預(yù)期,但是當我們使用@Configuration標注配置類時,執(zhí)行結(jié)果如下:

foo() invoked...
foo() 方法的 foo hashcode: 849373393
eoo() invoked...
eoo() 方法的 foo hashcode: 849373393

這里可以看到foo()方法只執(zhí)行了一次,同時eoo()方法調(diào)用foo()生成的foo對象是同一個。這也就是@Component和@Configuration的區(qū)別現(xiàn)象展示,那么為什么會有這樣的一個現(xiàn)象?我們來考慮一個問題,就是eoo()方法中調(diào)用了foo()方法,很明顯這個foo()這個方法就是會形成一個新對象,假設(shè)我們調(diào)用的foo()方法不是原來的foo()方法,是不是就可能不會形成新對象?如果我們在調(diào)用foo()方法的時候去容器中獲取一下foo這個Bean,是不是就可以達到這樣的效果?那如何才能達到這樣的效果呢?有一個方法,代理!換句話說,我們調(diào)用的eoo()和foo()方法,包括AppConfig都被Spring代理了,那么這里我們明白了@Component與@Configuration最根本的區(qū)別,那就是@Configuration標注的類會被Spring代理,其實這樣描述不是非常嚴謹,更加準確的來說應(yīng)該是如果一個類的BeanDefinition的Attribute中有Full配置屬性,那么這個類就會被Spring代理

3.Spring如何實現(xiàn)FULL配置的代理

如果要明白這一點,那么還需要明確一個前提,就是Spring在什么時間將這些配置類轉(zhuǎn)變成FULL模式或者LITE模式的,接下來我們就要介紹個人認為在Spring中非常重要的一個類,ConfigurationClassPostProcessor。

3.1ConfigurationClassPostProcessor是什么

首先來簡單的看一下這個類的定義:

public class ConfigurationClassPostProcessor implements 
                                              BeanDefinitionRegistryPostProcessor,
                                               PriorityOrdered, 
                                               ResourceLoaderAware, 
                                               BeanClassLoaderAware, 
                                               EnvironmentAware {}

由這個類定義可知這個類的類型為BeanDefinitionRegistryPostProcessor,以及實現(xiàn)了眾多Spring內(nèi)置的Aware接口,如果了解Beanfactory的后置處理器,那應(yīng)該清楚ConfigurationClassPostProcessor的執(zhí)行時機,當然不了解也沒有問題,我們會在后面將整個流程闡述清楚,現(xiàn)在需要知道的是ConfigurationClassPostProcessor這個類是在什么時間被實例化的?

3.2ConfigurationClassPostProcessor在什么時間被實例化

要回答這個問題,需要先明確一個前提,那就是ConfigurationClassPostProcessor這個類對應(yīng)的BeanDefinition在什么時間注冊到Spring的容器中的,因為Spring的實例化比較特殊,主要是基于BeanDefinition來處理的,那么現(xiàn)在這個問題就可以轉(zhuǎn)變?yōu)镃onfigurationClassPostProcessor這個類是在什么時間被注冊為一個Beandefinition的?這個可以在源代碼中找到答案,具體其實就是在初始化這個Spring容器的時候。

new AnnotationConfigApplicationContext(ConfigClass.class)
  -> new AnnotatedBeanDefinitionReader(this);
    -> AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
      -> new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        -> registerPostProcessor(BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName)

從這里可以看出,ConfigurationClassPostProcessor已經(jīng)被注冊為了一個BeanDefinition,上面我們講了Spring是通過對BeanDefinition進行解析,處理,實例化,填充,初始化以及眾多回調(diào)等等步驟才會形成一個Bean,那么現(xiàn)在ConfigurationClassPostProcessor既然已經(jīng)形成了一個BeanDefinition。

3.3 @Component與@Configuration的實現(xiàn)區(qū)別

上面ConfigurationClassPostProcessor已經(jīng)注冊到BeanDefinition注冊中心了,說明Spring會在某個時間點將其處理成一個Bean,那么具體的時間點就是在BeanFactory所有的后置處理器的處理過程。

AbstractApplicationContext
  -> refresh()
    -> invokeBeanFactoryPostProcessors(beanFactory);
      -> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

這個處理BeanFactory的后置處理器的方法比較復(fù)雜,簡單說來就是主要處理所有實現(xiàn)了BeanFactoryPostProcessor及BeanDefinitionRegistryPostProcessor的類,當然ConfigurationClassPostProcessor就是其中的一個,那么接下來我們看看實現(xiàn)的方法:

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

這兩個方法應(yīng)該就是ConfigurationClassPostProcessor最為關(guān)鍵的了,我們在這里先簡單的總結(jié)一下,第一個方法主要完成了內(nèi)部類,@Component,@ComponentScan,@Bean,@Configuration,@Import等等注解的處理,然后生成對應(yīng)的BeanDefinition,另一個方法就是對@Configuration使用CGLIB進行增強,那我們先來看Spring是在哪里區(qū)分配置的LITE模式和FULL模式?在第一個方法中有一個checkConfigurationClassCandidate方法:

public 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;
        }
        // ...      
      }

根據(jù)程序的判斷可知,如果一個類被@Configuration標注且代理模式為true,那么這個類對應(yīng)的BeanDefinition將會被Spring添加一個FULL配置模式的屬性,有些同學(xué)可能對這個”屬性“不太理解,這里可以簡單說一下,其實這個”屬性“在Spring中有一個特定的接口就是AttributeAccessor,BeanDefinition就是繼承了這個接口,如何理解這個AttributeAccessor呢?其實也很簡單,想想看,BeanDefinition主要是做什么的?這個主要是用來描述Class對象的,例如這個Class是不是抽象的,作用域是什么,是不是懶加載等等信息,那如果一個Class對象有一個“屬性”是BeanDefinition描述不了的,那這個要如何處理呢?那這個接口AttributeAccessor又派上用場了,你可以向其中存放任何你定義的數(shù)據(jù),可以理解為一個map,現(xiàn)在了解BeanDefinition的屬性的含義了么?

在這里也能看到@Configuration(proxyBeanMethods = false)和@Component一樣效果,都是LITE模式

在這里第一步先判斷出這個類是FULL模式還是LITE模式,那么下一步就需要開始執(zhí)行對配置類的注解的解析了,在ConfigurationClassParser這個類有一個processConfigurationClass方法,里面有一個doProcessConfigurationClass方法,這里就是解析前文所列舉的@Component等等注解的過程,解析完成之后,在ConfigurationClassPostProcessor類的方法processConfigBeanDefinitions,有一個loadBeanDefinitions方法,這個方法就是將前文解析成功的注解數(shù)據(jù)全都注冊成BeanDefinition,這就是ConfigurationClassPostProcessor這個類的第一個方法所完成的任務(wù),另外這個方法在這里是非常簡單的描述了一下,實際上這個方法非常的復(fù)雜,需要慢慢的研究。

接下來再說ConfigurationClassPostProcessor類的enhanceConfigurationClasses方法,這個方法主要完成了對@Configuration注解標注的類的增強,進行CGLIB代理,代碼如下:

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
        Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<String, AbstractBeanDefinition>();
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {//判斷是否被@Configuration標注
                if (!(beanDef instanceof AbstractBeanDefinition)) {
                    throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
                            beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
                }
                else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
                    logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
                            "' since its singleton instance has been created too early. The typical cause " +
                            "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
                            "return type: Consider declaring such methods as 'static'.");
                }
                configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
            }
        }
        if (configBeanDefs.isEmpty()) {
            // nothing to enhance -> return immediately
            return;
        }
        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);
            try {
                // Set enhanced subclass of the user-specified bean class
                Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
                Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);//生成代理的class
                if (configClass != enhancedClass) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " +
                                "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
                    }
                    //替換class,將原來的替換為CGLIB代理的class
                    beanDef.setBeanClass(enhancedClass);
                }
            }
            catch (Throwable ex) {
                throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
            }
        }
    }

這里需要CGLIB的一些知識,我就簡單的在這里總結(jié)一下,這個方法從所有的BeanDefinition中找到屬性為FULL模式的BeanDefinition,然后對其進行代理增強,設(shè)置BeanDefinition的beanClass。然后在增強時有一些細節(jié)稍微需要明確一下,就是我們這個普通類中的方法,比如eoo(),foo()等方法,將會被MethodInterceptor所攔截,這個方法的調(diào)用將會被BeanMethodInterceptor所代理,到這里我們大家應(yīng)該稍微明確了ConfigurationClassPostProcessor是在什么時間被實例化,什么時間解析注解配置,什么時間進行配置增強。如果看到這里不太明白,那歡迎與我來討論。

4.總結(jié)

@Component在Spring中是代表LITE模式的配置注解,這種模式下的注解不會被Spring所代理,就是一個標準類,如果在這個類中有@Bean標注的方法,那么方法間的相互調(diào)用,其實就是普通Java類的方法的調(diào)用。

@Configuration在Spring中是代表FULL模式的配置注解,這種模式下的類會被Spring所代理,那么在這個類中的@Bean方法的相互調(diào)用,就相當于調(diào)用了代理方法,那么在代理方法中會判斷,是否調(diào)用getBean方法還是invokeSuper方法,這里就是這兩個注解的最根本的區(qū)別。

一句話概括就是 @Configuration 中所有帶 @Bean 注解的方法都會被動態(tài)代理,因此調(diào)用該方法返回的都是同一個實例。 

到此這篇關(guān)于Spring中@Configuration和@Component注解的區(qū)別及原理的文章就介紹到這了,更多相關(guān)@Configuration和@Component注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java?SpringAOP技術(shù)之注解方式詳解

    Java?SpringAOP技術(shù)之注解方式詳解

    這篇文章主要為大家詳細介紹了Java?SpringAOP技術(shù)之注解方式,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-02-02
  • java一個數(shù)據(jù)整理的方法代碼實例

    java一個數(shù)據(jù)整理的方法代碼實例

    這篇文章主要介紹了java一個數(shù)據(jù)整理的方法代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-09-09
  • 基于Java編寫一個PDF與Word文件轉(zhuǎn)換工具

    基于Java編寫一個PDF與Word文件轉(zhuǎn)換工具

    前段時間一直使用到word文檔轉(zhuǎn)pdf或者pdf轉(zhuǎn)word,尋思著用Java應(yīng)該是可以實現(xiàn)的,于是花了點時間寫了個文件轉(zhuǎn)換工具,感興趣的可以了解一下
    2023-01-01
  • SpringBoot 中 AutoConfiguration的使用方法

    SpringBoot 中 AutoConfiguration的使用方法

    這篇文章主要介紹了SpringBoot 中 AutoConfiguration的使用方法,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-04-04
  • Netty分布式ByteBuf使用subPage級別內(nèi)存分配剖析

    Netty分布式ByteBuf使用subPage級別內(nèi)存分配剖析

    這篇文章主要為大家介紹了Netty分布式ByteBuf使用subPage級別內(nèi)存分配剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-03-03
  • datatables 帶查詢條件java服務(wù)端分頁處理實例

    datatables 帶查詢條件java服務(wù)端分頁處理實例

    本篇文章主要介紹了datatables 帶查詢條件java服務(wù)端分頁處理實例,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • SpringMVC中文亂碼踩坑記錄

    SpringMVC中文亂碼踩坑記錄

    這篇文章主要介紹了SpringMVC中文亂碼踩坑記錄,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-08-08
  • 理解 Java 核心基礎(chǔ)精髓解析

    理解 Java 核心基礎(chǔ)精髓解析

    這篇文章主要介紹了解 Java 核心基礎(chǔ)精髓解析問題,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-03-03
  • Spring Data JPA踩坑記錄(@id @GeneratedValue)

    Spring Data JPA踩坑記錄(@id @GeneratedValue)

    這篇文章主要介紹了Spring Data JPA踩坑記錄(@id @GeneratedValue),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • Java8使用stream實現(xiàn)list中對象屬性的合并(去重并求和)

    Java8使用stream實現(xiàn)list中對象屬性的合并(去重并求和)

    這篇文章主要介紹了Java8使用stream實現(xiàn)list中對象屬性的合并(去重并求和),本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-01-01

最新評論