在 Spring Boot 開發中,@Autowired 是依賴注入(DI)最常用的方式。但有時候某個 Bean 不一定存在,或者在測試環境中需要排除/替換某個 Bean,這時就需要一些技巧來處理。
@Autowired(required = false)
預設情況下,@Autowired 如果找不到對應的 Bean,Spring 會拋出 NoSuchBeanDefinitionException。設定 required = false 後,找不到 Bean 時該欄位會保持 null,不會報錯。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Service
public class MyService {
@Autowired(required = false)
private OptionalDependency optionalDependency;
public void doSomething() {
if (optionalDependency != null) {
optionalDependency.execute();
} else {
// 走備用邏輯
}
}
}
|
也可以搭配 Java 8 的 Optional:
1
2
3
4
5
6
|
@Autowired
private Optional<OptionalDependency> optionalDependency;
public void doSomething() {
optionalDependency.ifPresent(dep -> dep.execute());
}
|
@ConditionalOnMissingBean
@ConditionalOnMissingBean 用於自動組態(Auto Configuration)時,當容器中沒有指定類型的 Bean 時才建立。常用在 Starter 或自訂組態類別中:
1
2
3
4
5
6
7
8
9
10
11
12
|
@Configuration
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean(DataSource.class)
public DataSource defaultDataSource() {
// 只有在沒有其他 DataSource Bean 時才建立這個預設的
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
|
@Primary 與 @Qualifier
當容器中有多個相同類型的 Bean 時,Spring 不知道該注入哪一個,會拋出 NoUniqueBeanDefinitionException。
@Primary:指定預設的 Bean
1
2
3
4
5
6
7
8
9
10
|
@Bean
@Primary
public DataSource primaryDataSource() {
return new HikariDataSource(/* 主要資料來源設定 */);
}
@Bean
public DataSource secondaryDataSource() {
return new HikariDataSource(/* 次要資料來源設定 */);
}
|
不指定時,@Autowired 預設注入標有 @Primary 的那個。
@Qualifier:明確指定注入哪個 Bean
1
2
3
|
@Autowired
@Qualifier("secondaryDataSource")
private DataSource dataSource;
|
測試時排除或 Mock Bean
使用 @MockBean(Spring Boot Test)
在測試中,可以用 @MockBean 將某個 Bean 替換成 Mockito Mock 物件,原本的實作不會被執行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@SpringBootTest
class MyServiceTest {
@MockBean
private ExternalApiClient externalApiClient; // 替換成 Mock
@Autowired
private MyService myService;
@Test
void testDoSomething() {
when(externalApiClient.call()).thenReturn("mocked result");
String result = myService.doSomething();
assertEquals("mocked result", result);
}
}
|
使用 @TestConfiguration 覆蓋 Bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@SpringBootTest
class MyServiceTest {
@TestConfiguration
static class TestConfig {
@Bean
@Primary
public ExternalApiClient fakeApiClient() {
return () -> "fake result"; // 替換實作
}
}
@Autowired
private MyService myService;
}
|
使用 Profile 排除 Bean
1
2
3
4
5
|
@Bean
@Profile("!test") // 非 test 環境才建立
public SomeHeavyBean heavyBean() {
return new SomeHeavyBean();
}
|
測試時啟用 test profile,就不會載入這個 Bean:
1
2
3
|
@SpringBootTest
@ActiveProfiles("test")
class MyTest { ... }
|
參考資料