Pozostając w kontekście rejestrowania beanów wykorzystując w tym celu adnotację @Configuration. Natrafiłem ostatnio na ciekawe rozwiązanie dostępne out-of-the-box w Springu. Chodzi o opcjonalność zależności dostarczanych dla tworzonego beana. Jeśli oczekiwany w parametrze metody, oznaczonej przez adnotację @Bean, parametr nie zostanie znaleziony (taki bean nie został zarejestrowany) to niekoniecznie musi to oznaczać, że aplikacja nie wstanie. Po prostu możemy żyć dalej i np. utworzyć jakąś domyślną “zależność”. Lepiej to oczywiście będzie pokazać na przykładzie.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface BaseInterface {
}

public class BaseDefaultClass implements BaseInterface {
}

public class BaseSpecifiedClass implements BaseInterface {
}

public class AggregationClass {

    public final BaseInterface baseInterface;

    public AggregationClass(BaseInterface baseInterface) {
        this.baseInterface = baseInterface;
    }

}

Teraz należy stworzyć klasę konfiguracyjną rejestrującą bean klasy AggregationClass. Jednak w metodzie oddelegowanej do tego zadania zamiast korzystać z konkretnego typu w parametrze to użyjemy wrappera w postaci Optional. W ten sposób jeśli bean typu BaseInterface nie zostanie znaleziony to Optional będzie pusty, a my będziemy mogli skorzystać z jakiejś domyślnej implementacji. W innym przypadku znajdzie się tam oczekiwana przez nas instancja.

Oczywiście całość rozwiązania powstała w oparciu o dokumentację Springa.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Configuration(proxyBeanMethods = false)
public class ConfigurationClass {

    @Bean
    public AggregationClass aggregationClass(Optional<BaseInterface> baseInterface) {
        return new AggregationClass(baseInterface.orElseGet(() -> new BaseDefaultClass()));
    }

    @Bean
    public BaseSpecifiedClass baseSpecifiedClass() {
        return new BaseSpecifiedClass();
    }

}

@SpringBootTest
class ConfigurationOptionalClassTest {

    @Autowired
    AggregationClass aggregationClass;

    @Test
    void beanCreation() {
        assertInstanceOf(BaseSpecifiedClass.class, aggregationClass.baseInterface);
    }

}

Test tylko potwierdził oczekiwane zachowanie. Oczywiście warto jeszcze sprawdzić sytuację, w której nie zarejestrujemy beana typu BaseInterface. Czy faktycznie za polem baseInterface klasy AggregationClass będzie się kryła instancja typu BaseDefaultClass?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public class ConfigurationClass {

    @Bean
    public AggregationClass aggregationClass(Optional<BaseInterface> baseInterface) {
        return new AggregationClass(baseInterface.orElseGet(BaseDefaultClass::new));
    }

}

@SpringBootTest
class ConfigurationOptionalClassTest {

    @Autowired
    AggregationClass aggregationClass;

    @Test
    void beanCreation() {
        assertInstanceOf(BaseDefaultClass.class, aggregationClass.baseInterface);
    }

}

Jak najbardziej, test pozytywnie zweryfikował powyższą tezę. Moim zdaniem warto mieć gdzieś z tyłu głowy to rozwiązanie. Być może przyda się w jakiś ekstremalnych przypadkach. A jeśli nie to uważam, że na rozmowie rekrutacyjnej zaplusujemy pokazując, że znamy trochę bardziej narzędzie, z którego korzystamy niż tylko na poziomie podstawowym.