1. 概述
在本教程中,我們將對 Spring 的 @Component 註解及其相關領域進行全面瞭解。我們將看到我們如何與一些核心 Spring 功能集成,以及如何利用其眾多優勢。
2. Spring ApplicationContext
在理解 @Component 的價值之前,我們需要先了解一些關於 Spring ApplicationContext 的基本知識。
Spring ApplicationContext 是 Spring 存儲並自動管理和分發的對象實例的地方。這些對象被稱為 Bean。
Spring 的一些主要功能包括 Bean 管理和依賴注入的機會。
利用控制反轉原則,Spring 收集應用程序中的 Bean 實例並在適當的時候使用它們。 我們無需處理這些對象的設置和實例化,即可向 Spring 展示 Bean 依賴關係。
能夠使用諸如 @Autowired 這樣的註解將 Spring 管理的 Bean 注入到我們的應用程序中,是創建強大且可擴展的 Spring 代碼的關鍵驅動力。
那麼,我們如何告訴 Spring 我們想要它管理哪些 Bean? 我們應該利用 Spring 的自動 Bean 檢測功能,通過在我們的類中使用 Stereotype 註解。
3. <em @Component>
@Component 是一個註解,允許 Spring 自動檢測我們的自定義 Bean。
換句話説,無需編寫任何顯式代碼,Spring 將會:
- 掃描應用程序中帶有 @Component 註解的類
- 實例化它們並注入任何指定的依賴項
- 在需要的地方注入它們
然而,大多數開發者更傾向於使用更專業的 Stereotype 註解來執行此功能。
3.1. Spring 樣板註解
Spring 提供了幾個樣板註解,包括 @Controller、@Service 和 @Repository。它們的功能與 @Component 相同。
它們的功能相同,因為它們都是由 @Component 作為元註解構成的複合註解。 它們就像 @Component 的別名,具有專門用途和在 Spring 自動檢測或依賴注入之外的含義。
如果我們需要使用註解來自動檢測 Bean,理論上我們可以只使用 @Component。 另一方面,我們也可以將這些專門註解組合使用,並使用 @Component。
然而,在 Spring 的其他領域,它們會專門查找 Spring 的樣板註解,以提供額外的自動化優勢。 因此,我們最好大多數情況下堅持使用這些已建立的樣板。
以下是一個每個情況的示例,在我們的 Spring Boot 項目中:
@Controller
public class ControllerExample {
}
@Service
public class ServiceExample {
}
@Repository
public class RepositoryExample {
}
@Component
public class ComponentExample {
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface CustomComponent {
}
@CustomComponent
public class CustomComponentExample {
}我們編寫一個測試,以證明每個組件都能被 Spring 自動檢測並添加到 ApplicationContext 中:
@SpringBootTest
@ExtendWith(SpringExtension.class)
public class ComponentUnitTest {
@Autowired
private ApplicationContext applicationContext;
@Test
public void givenInScopeComponents_whenSearchingInApplicationContext_thenFindThem() {
assertNotNull(applicationContext.getBean(ControllerExample.class));
assertNotNull(applicationContext.getBean(ServiceExample.class));
assertNotNull(applicationContext.getBean(RepositoryExample.class));
assertNotNull(applicationContext.getBean(ComponentExample.class));
assertNotNull(applicationContext.getBean(CustomComponentExample.class));
}
}3.2. <em @ComponentScan</em>>
在完全依賴 <em @Component</em>> 之前,我們必須理解它只是一個簡單的註解。該註解的作用是區分 Bean 與其他對象,例如領域對象。
但是,Spring 使用 <em @ComponentScan</em>> 註解將它們收集到其 <em ApplicationContext</em>> 中。
如果我們在編寫 Spring Boot 應用程序,那麼知道 <em @SpringBootApplication</em>> 是一個組合註解,其中包含 <em @ComponentScan</em>> 非常有用。只要我們的 <em @SpringBootApplication</em>> 類位於項目的根目錄下,默認情況下它會掃描我們定義的每一個 <em @Component</em>>。
但是,如果我們的 <em @SpringBootApplication</em>> 類不能位於項目的根目錄下,或者我們想掃描外部來源,我們可以顯式配置 <em @ComponentScan</em>> 以查找我們指定的任何包,只要該包存在於類路徑上。
讓我們定義一個不在掃描範圍內的 <em @Component</em>> Bean:
package com.baeldung.component.scannedscope;
@Component
public class ScannedScopeExample {
}接下來,我們也可以通過向我們的 @ComponentScan 註解提供明確指示來包含它:
package com.baeldung.component.inscope;
@SpringBootApplication
@ComponentScan({"com.baeldung.component.inscope", "com.baeldung.component.scannedscope"})
public class ComponentApplication {
//public static void main(String[] args) {...}
}最後,我們可以測試它是否存在:
@Test
public void givenScannedScopeComponent_whenSearchingInApplicationContext_thenFindIt() {
assertNotNull(applicationContext.getBean(ScannedScopeExample.class));
}當我們需要掃描項目中的外部依賴項時,這種情況更有可能發生。
3.3. <em @Component</em> 的侷限性
存在一些場景,我們希望特定對象在無法使用 <em @Component</em> 的情況下,成為 Spring 管理的 Bean。
讓我們定義一個在項目外部的包中帶有 <em @Component</em> 註解的對象:
package com.baeldung.component.outsidescope;
@Component
public class OutsideScopeExample {
}這是一個測試,證明 ApplicationContext 不包含外部組件。
@Test
public void givenOutsideScopeComponent_whenSearchingInApplicationContext_thenFail() {
assertThrows(NoSuchBeanDefinitionException.class, () -> applicationContext.getBean(OutsideScopeExample.class));
}此外,由於代碼來自第三方來源,我們可能無法訪問源代碼,因此也無法添加 @Component 註解。或者,我們可能希望根據運行的環境,條件地使用一種 Bean 實現方式,而另一種實現方式。 自動檢測通常足夠,但當它不適用時,我們可以使用 @Bean。
4. @Component 與 @Bean
@Bean 也是 Spring 使用的一種註解,用於在運行時收集 Bean,但它不應用於類級別。相反,我們通過註解方法,使用 @Bean,以便 Spring 將方法的返回值存儲為 Spring Bean。
我們首先創建一個沒有註解的 POJO:
public class BeanExample {
}在我們的類中,如果該類被註解為 @Configuration</em/>,我們可以創建生成 Bean 的方法:
@Bean
public BeanExample beanExample() {
return new BeanExample();
}BeanExample可能代表一個本地類,也可能是一個外部類。這並不重要,因為我們需要返回它的一個實例。
然後我們可以編寫一個測試,以驗證Spring是否正確地獲取了該Bean:
@Test
public void givenBeanComponents_whenSearchingInApplicationContext_thenFindThem() {
assertNotNull(applicationContext.getBean(BeanExample.class));
}我們應注意到由於 @Component 和 @Bean 之間的差異,存在一些重要的影響。
- @Component 是類級別的註解,而 @Bean 是方法級別的,因此 @Component 僅在類的源代碼可編輯時才是一個選項。 @Bean 始終可用,但更冗長。
- @Component 與 Spring 的自動檢測兼容,而 @Bean 需要手動類實例化。
- 使用 @Bean 可以解耦 Bean 的實例化與其類定義,這就是為什麼我們可以使用它將第三方類轉換為 Spring Bean 的原因。 這也意味着我們可以引入邏輯來決定使用 Bean 的多個可能實例選項中的哪一個。
5. 結論
我們剛剛探討了 Spring 的 <em @Component 註解以及其他相關主題。首先,我們討論了各種 Spring 樣板註解,這些註解只是 <em @Component 的特化版本。
然後,我們瞭解到 <em @Component 在 @ComponentScan 能夠找到它時不會執行任何操作。
最後,由於無法在類上使用 <em @Component(因為我們沒有源代碼),我們學習瞭如何使用 <em @Bean 註解代替。