1. 概述
在本教程中,我們將學習如何使用 Spring 的 @Import 註解,同時闡明它與 @ComponentScan 註解的區別。
2. 配置與 Bean
在理解 <em @Import</em> 註解之前,我們需要了解什麼是 Spring Bean,並對 <em @Configuration</em> 註解有基本的瞭解。
這兩個主題超出了本教程的範圍。但是,我們可以在我們的 Spring Bean 文章和 Spring 文檔 中學習它們。
讓我們假設我們已經準備好了三個 Bean – Bird、Cat 和 Dog ,每個 Bean 都有自己的配置類。
然後,我們可以使用這些 Config 類為我們的上下文提供配置:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { BirdConfig.class, CatConfig.class, DogConfig.class })
class ConfigUnitTest {
@Autowired
ApplicationContext context;
@Test
void givenImportedBeans_whenGettingEach_shallFindIt() {
assertThatBeanExists("dog", Dog.class);
assertThatBeanExists("cat", Cat.class);
assertThatBeanExists("bird", Bird.class);
}
private void assertThatBeanExists(String beanName, Class<?> beanClass) {
Assertions.assertTrue(context.containsBean(beanName));
Assertions.assertNotNull(context.getBean(beanClass));
}
}3. 使用 @Import 組建配置
聲明所有配置沒有問題。但 想象一下,在不同的來源中控制數十個配置類所帶來的麻煩。 應該有更好的方法。
@Import 註解提供了一種解決方案,它通過將 Configuration 類分組來解決這個問題。
@Configuration
@Import({ DogConfig.class, CatConfig.class })
class MammalConfiguration {
}現在,我們只需要記住 哺乳動物:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { MammalConfiguration.class })
class ConfigUnitTest {
@Autowired
ApplicationContext context;
@Test
void givenImportedBeans_whenGettingEach_shallFindOnlyTheImportedBeans() {
assertThatBeanExists("dog", Dog.class);
assertThatBeanExists("cat", Cat.class);
Assertions.assertFalse(context.containsBean("bird"));
}
private void assertThatBeanExists(String beanName, Class<?> beanClass) {
Assertions.assertTrue(context.containsBean(beanName));
Assertions.assertNotNull(context.getBean(beanClass));
}
}當然,以下是翻譯後的內容:
嗯,我們可能很快就會忘記 Bird,所以我們來做一個額外的組,包含所有 animal 配置類:
@Configuration
@Import({ MammalConfiguration.class, BirdConfig.class })
class AnimalConfiguration {
}最後,沒有人被遺漏,我們只需要記住一個類:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { AnimalConfiguration.class })
class AnimalConfigUnitTest {
// same test validating that all beans are available in the context
}4. @Import 與 @ComponentScan 的比較
在繼續介紹 @Import 示例之前,我們先暫停一下,並將其與 @ComponentScan 進行比較。
4.1. 相似性
兩個註解可以接受任何 <em @Component</em> 或 <em @Configuration</em> 類。
讓我們使用 @<em @Component</em> 添加一個新的組件:
// 添加一個新的組件
@Component
@Import({AnotherComponent.class})
public class MyComponent {
// ...
}
@Configuration
@Import(Bug.class)
class BugConfig {
}
@Component(value = "bug")
class Bug {
}現在,Bug豆與任何其他豆類一樣可用。
4.2. 概念差異
簡單來説,我們可以以兩種標註方式達成相同的結果。那麼,它們之間有什麼區別呢?
為了回答這個問題,讓我們回顧一下,Spring 通常提倡 約定優於配置 的方法。
將此概念與我們的標註進行類比,@ComponentScan 更加類似於約定,而 @Import 則更像配置。
4.3. 實際應用中發生的事情
通常,我們開始使用應用程序時,會使用 <em >ComponentScan</em> 在根包中,以便它可以自動為我們查找所有組件。如果使用 Spring Boot,則 <em >@SpringBootApplication</em> 已經包含 <em >@ComponentScan</em>,我們就可以直接使用。這充分體現了約定優於配置的優勢。
現在,讓我們假設我們的應用程序正在快速增長。這時,我們需要處理來自各種地方的 Bean,例如組件、不同包結構以及由我們自己和第三方構建的模塊。
在這種情況下,將所有內容都添加到上下文中可能會導致 Bean 衝突。此外,啓動時間也可能變慢。
另一方面,我們不想為每個新組件編寫 @<em >Import</em>,因為這樣做既不高效也不實用。
例如,考慮我們的動物。我們確實可以將 @<em >Import</em> 隱藏在上下文聲明中,但我們仍然需要記住每個 <em >Config</em> 類的 @<em >Import</em>。
4.4. 協同工作
我們可以追求雙贏局面。假設我們有一個專門用於 動物 的包。它也可以是一個組件或模塊,保持相同的理念。
然後,我們可以有一個專門用於 @ComponentScan 的組件,用於 動物 包。
package com.baeldung.importannotation.animal;
// imports...
@Configuration
@ComponentScan
public class AnimalScanConfiguration {
}並且使用 @Import 來保持對上下文添加內容的控制:
package com.baeldung.importannotation.zoo;
// imports...
@Configuration
@Import(AnimalScanConfiguration.class)
class ZooApplication {
}最後,任何添加到 animal 包中的新 Bean 都將被我們的上下文自動發現。我們仍然對使用的配置擁有明確的控制權。
5. 結論
在本快速教程中,我們學習瞭如何使用 @Import 來組織我們的配置。
我們還了解到 @Import 與 @ComponentScan 非常相似, 區別在於 @Import 採用顯式方法,而 @ComponentScan 採用隱式方法。
此外,我們還探討了在實際應用中控制配置可能遇到的困難,以及如何通過結合使用這兩種註解來解決這些問題。