這篇先說明用法,下篇分析以下場景是如何將 Bean 註冊進 IOC容器的。
這種用法在項目中是非常常見的,基本上是必有。我們來看下用法:
這樣壹個 Bean 就註冊進 IOC 容器了,Bean 的名稱默認是方法名,並且是不會轉換大小寫的,也就是假如妳的方法名是 TestBean() ,那麽 Bean 的名稱就是 TestBean 。當然我們也可以使用 name 或者 value 指定 Bean 的名稱,比如 @Bean(value = "testBean"),如果二者同時存在則會報錯。
我們來看下其他屬性:
autowireCandidate:默認值是 true 。如果設置為 false 的話,那麽通過 byType 的方式獲取 Bean 就會報錯,當然我們可以使用 Resource 註解獲取。
initMethod:在 Bean 實例化後調用的初始化方法,值是 Bean 類中的方法名。
destroyMethod:在 Bean 要銷毀時調用的清理方法,值是 Bean 類中的方法名。
@Bean 註解只能定義在 @Configuration 類下嗎? NO NO NO,它可以定義在任意能被 IOC 掃描的註解下,比如 @Component註解,至於區別,下篇再講。
先講普通用法:
深度用法:
ComponentScan 註解中有兩個這樣的屬性:includeFilters 與 excludeFilters,前壹個是只包含規則,後壹個是排除包含規則,他們的值是壹個 @Filter 註解的形式,Filter 中的 type 有 5 中類型,分別如下。
1、ANNOTATION
第壹種是以註解的形式包含或不包含,比如:
這裏邊要配置useDefaultFilters = false 禁用默認規則,因為默認規則是掃描所有,配只包含就沒用了。這裏的意思只掃描 Configuration 註解。
2、ASSIGNABLE_TYPE
這種是包含我們給定的類型,不管是給定的類型和子類都會被包含進 IOC 容器。
然後我們發現 testBean 註冊進去了,為什麽我們不標註 @Component 這樣的註解實例也會被註冊進 IOC 呢?因為 ComponentScan 會掃描包下所有文件,只要符合我們定義的過濾規則,它就會將 Bean 註冊進 IOC 容器中。
3、ASPECTJ
ASPECTJ 是使用 aspectj 表達式
4、REGEX
REGEX 是使用正則表達式
5、CUSTOM
這種呢就是我們 SpringBootApplication 註解用到的方式了,我來解釋壹下具體規則:這種方式是可以自己自定義掃描規則,它接受壹個實現 TypeFilter 接口的類。
當它掃描類的時候掃描到了 TestBean,然後符合了我的匹配規則(也就是返回true)就註冊進去了。
下面的例子中,我們直接看 Spring 源碼的實現比較具有代表性壹點。
我們點進 @EnableTransactionManagement 註解中,發現了這個 @Import(TransactionManagementConfigurationSelector.class),它的作用就是將類導入,類會被註冊進 IOC 容器中。
這個註解放置的位置要是 Spring 能掃描到的地方,不然 Spring 也不會主動去解析這個註解。
如果我們自己要使用註解的話,我們可以做個類似於 EnableTransactionManagement 的功能插拔式導入配置類,這樣就可以實現動態開啟壹些 Bean 了。
我們還是來看下 TransactionManagementConfigurationSelector 這個類,看下它的繼承關系發現它間接性的實現了 ImportSelector 接口,主要看它實現的這個方法:
這個方法的作用就是根據妳返回的類全限定名(org.springframework.context.annotation.AutoProxyRegistrar)數組來創建 Bean 。
實現了 ImportSelector 的類也是需要使用 @Import 導入。
這個我們來看下 @MapperScan (org.mybatis.spring.annotation)導入的 MapperScannerRegistrar 發現它實現了 ImportBeanDefinitionRegistrar:
它的作用是拿到 BeanDefinitionRegistry Bean 的定義信息,然後往裏面加 BeanDefinition 就會將相應的對象註冊進去,它更深入的就不說了,實際上就是解析下註解屬性,然後掃描相應的包下的類註冊 Bean。我們自己搞個簡單的。
這樣就註冊了壹個 Bean 名稱是 testBean 類型是 TestBean 類型的 Bean 了。
如果註冊的是壹個有參構造器呢?那就這樣:
addConstructorArgValue 根據構造器參數的順序去添加。
實現了 ImportBeanDefinitionRegistrar 的類也是需要使用 @Import 導入。
然後 TestBean 就註冊進去了,打印的時候我們發現 Bean 的名稱是 MyFactoryBean 的全限定名,但是它的類型是 TestBean 類型的,如果想要獲取 MyFactoryBean 類型的 Bean 的話,通過 Bean 名稱為 &myFactoryBean 就能獲取到。
在我們的Spring Boot項目中,壹般都是只掃描主類下的所有類,然後將壹些被特定註解標註的類加載到IOC容器,但是如果我們將包分離,我們又如何更加方便的將其他包的類加載進來呢? spring boot提供了壹種類似於Java的SPI(服務發現)機制spring.factories,只要在resources目錄下創建META-INF文件夾,再創建 spring.factories文件,然後再裏面配置
這樣在導入當前包的就會自動掃描spring.factories文件,解析後將裏面的壹些類加載到IOC容器中。具體的實現代碼在spring-core的SpringFactoriesLoader類中。
這些就不講了。