Contents

Spring Boot 排除 autowired 方法

在 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 { ... }

參考資料