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:過濾規則,根據不同的過濾類型配置不同的規則
- type:過濾類型
- 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()); }
總結
註冊組件的方式:
- 包掃描 (@ComponentScan) + 組件註解(@Component / @Controller / @Service / @Repository)
- @Bean [導入第三方包里的組件]
- @Import [快速給容器中導入組件]
- 普通類:直接註冊,id默認是全類名
- ImportSelector:返回需要註冊組件的全類名數組
- ImportBeanDefinitionRegistrar: 手動註冊組件到容器中
- 使用 spring 提供的 FactoryBean(工廠Bean)
- 默認獲取的是 FactoryBean 調用getObject方法返回的對象
- 可以在獲取Bean的時候在 id 前面加上 & 符號,獲取 FactoryBean 本身