Optional dependency przy tworzeniu bean
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.
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.
@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?
@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.