Spring註解 – 組件的註冊

  • 2020 年 3 月 17 日
  • 筆記

Spring Boot的出現極大的簡化了我們的開發,讓我們無需再寫繁雜的配置文件,其正是利用了註解的便捷性,而Spring Boot又依賴於Spring,因此深入學習Spring的註解是十分必要的。

組件註冊相關註解

@Configuration

寫在類上,聲明此類是一個配置類,替代xml文件

@Bean

作用:

​ 給 IOC 容器中註冊一個Bean,一般添加在方法上,組件類型為方法的返回值,id默認為方法名稱

常用屬性:

  • value / name:指定組件的名稱,如果不指定,默認是方法名
  • initMethod:指定初始化方法
  • destroyMethod:指定銷毀方法

@ComponentScan

作用:

根據自定義的規則,自動掃描 IOC 容器中所有組件,在 jdk1.8 之後可以在一個類上定義多個@ComponentScan

還有一個@ComponentScans註解,也可以在裡面定義多個@ComponentScan

常用屬性:

  • value / basePackages:指定要掃描的包名
  • @Filter:用於指定過濾的規則
    • type:過濾類型
      • FilterType.ANNOTATION:按照註解的方式
      • FilterType.ASSIGNABLE_TYPE:按照給定的類型
      • FilterType.ASPECTJ:使用ASPECTJ表達式
      • FilterType.REGEX:使用正則表達式
      • FilterType.CUSTOM:自定義類型
    • value / classes:過濾值
    • pattern:過濾規則,根據不同的過濾類型配置不同的規則
  • useDefaultFilters:是否使用默認過濾規則, 默認是 true
  • includeFilters:指定掃描的時候只包含什麼組件,需要配置 useDefaultFilters 屬性為false
  • excludeFilters:指定掃描的時候按照什麼規則排除哪些組件
  • lazyInit:懶載入

如何使用FilterType.CUSTOM自定義過濾規則?

public class MyTypeFilter implements TypeFilter {        /**       * 匹配方法,確定此過濾器是否與給定元數據描述的類匹配       * @param metadataReader 讀取到的當前正在掃描的類的資訊       * @param metadataReaderFactory 可以獲取到其他任何類的資訊       * @return true:匹配, false:不匹配       * @throws IOException       */      @Override      public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {          //獲取當前類的註解資訊          AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();          //獲取當前正在掃描的類的資訊          ClassMetadata classMetadata = metadataReader.getClassMetadata();          //獲取當前類資源(類的路勁)          Resource resource = metadataReader.getResource();            //自定義匹配規則          String className = classMetadata.getClassName();          if(className.contains("Controller")){              return true;          }          return false;      }  }

@Scope

作用:

​ 調整作用域

常用參數:

  • value / scopeName
    • singleton:單實例,ioc容器啟動的時候就會調用方法,創建bean對象,以後每次獲取都是直接從ioc容器中取(map.get())。
    • prototype:多實例,ioc容器啟動的時候不會去調用,當從ioc容器中獲取bean對象的時候才會創建。
    • request:同一次請求創建一個實例。
    • session:同一個session範圍創建一個實例。
    • global session:全局session範圍創建一個實例,一般用於集群。

@Lazy

懶載入,一般用於單例模式,容器啟動的時候不會創建bean,第一次調用的時候才創建

@Conditional

作用:

​ 按照一定條件進行判斷,滿足條件才註冊bean,可以放在方法或類上,此註解在Spring Boot底層大量使用

常用參數:

  • value:傳入一個繼承了Condition介面的類(可傳入多個),類中定義自己需要的條件

@Import

作用:

​ 給ioc容器中導入指定的組件

常用參數:

  • value:傳入指定類,id默認是全類名。可傳入自定義類、ImportSelector 和 ImportBeanDefinitionRegistrar

用法:

​ 在配置類上添加如下形式的註解即可

@Import({Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})

ImportSelector

傳入實現了 ImportSelector 介面的類,返回一個全類名數組,好處就是可以自定義需要導入的組件

public class MyImportSelector implements ImportSelector {        /**       * 自定義導入的組件       * @param importingClassMetadata 當前標註了@Import註解的類的所有註解資訊       * @return 返回全類名       */      @Override      public String[] selectImports(AnnotationMetadata importingClassMetadata) {          return new String[]{"com.spring.color.Blue", "com.spring.color.Yellow"};      }  }

打斷點debug一下,可以看到參數的資訊,的確是當前標註@Import的類上的註解資訊

ImportBeanDefinitionRegistrar

手動註冊bean到容器中,調用 registerBeanDefinition() 方法

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {      /**       *       * @param importingClassMetadata 當前標註了@Import註解的類的所有註解資訊       * @param registry 容器中已註冊組件的資訊       */      @Override      public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {          int count = registry.getBeanDefinitionCount();          if(count > 10){              BeanDefinition beanDefinition = new RootBeanDefinition(Ten.class);              registry.registerBeanDefinition("ten", beanDefinition);          }      }  }

使用 FactoryBean 註冊組件

自定義一個類,實現 FactoryBean 介面

public class FoodFactoryBean implements FactoryBean<Food> {        /**       * 獲取實例對象       * @return       * @throws Exception       */      @Override      public Food getObject() throws Exception {          return new Food();      }        /**       * 獲取實例類型       * @return       */      @Override      public Class<?> getObjectType() {          return Food.class;      }        /**       * 是否單例,true:單例  false:多例       * @return       */      @Override      public boolean isSingleton() {          return true;      }  }

使用@Bean註冊到容器

@Bean  public FoodFactoryBean foodFactoryBean(){      return new FoodFactoryBean();  }

測試一下

@Test  public void test2(){      Object bean = applicationContext.getBean("foodFactoryBean");      System.out.println("foodFactoryBean 的類型:" + bean.getClass());  }

運行結果如下,發現類型竟然不是 FoodFactoryBean ,使用@Bean註冊的組件類型不是方法的返回值嗎?實際上,FoodFactoryBean註冊的時候調用的了 getObject() 方法,所以註冊的是 Food 類

foodFactoryBean 的類型:class com.spring.bean.Food

那麼如果想要獲得 FoodFactoryBean 類怎麼辦呢?

看一下 BeanFactory 的源碼,定義了一個成員變數 FACTORY_BEAN_PREFIX

這個變數用於取消引用 FactoryBean 實例,並將其與由 FactoryBean 創建的bean區別開。

例如,如果名為 test 的 bean 是 FactoryBean,則獲取 &test 將返回工廠,而不是工廠返回的實例。

所以在getBean的時候,在 id 前加上 & 即可

@Test  public void test2(){      Object bean = applicationContext.getBean("&foodFactoryBean");      System.out.println("foodFactoryBean 的類型:" + bean.getClass());  }

總結

註冊組件的方式:

  1. 包掃描 (@ComponentScan) + 組件註解(@Component / @Controller / @Service / @Repository)
  2. @Bean [導入第三方包里的組件]
  3. @Import [快速給容器中導入組件]
    • 普通類:直接註冊,id默認是全類名
    • ImportSelector:返回需要註冊組件的全類名數組
    • ImportBeanDefinitionRegistrar: 手動註冊組件到容器中
  4. 使用 spring 提供的 FactoryBean(工廠Bean)
    • 默認獲取的是 FactoryBean 調用getObject方法返回的對象
    • 可以在獲取Bean的時候在 id 前面加上 & 符號,獲取 FactoryBean 本身