Cykliczne zależności komponentów w Spring'u

31 października 2009 | 1 komentarze

Cykliczna zależność to sytuacja w której komponent A jest zależny od komponentu B i jednocześnie B jest zależny od A. W przypadku kontenera Spring IoC wzajemna zależność jest wykrywana i zgłaszana w postaci wyjątku BeanCreationException podczas startu aplikacji. Aplikacja oczywiście nie wstaje.

Przykładowy konfig Spring'a z cykliczną zależnością (/WEB-INF/application-context.xml):

<bean id="userService" class="pl.codeservice.service.UserService" >
 <property name="studioService" ref="studioService" />
</bean>

<bean id="studioService" class="pl.codeservice.service.StudioService" >
 <property name="userService" ref="userService" />
</bean>

Zalecane jest pozbycie się cykliczności jako złej praktyki bo:
-zwiększa stopień skomplikowania konfiguracji (OOP principles: loose coupling)
-powoduje problemy architektoniczne: który komponent ma być zniszczony jako pierwszy, istnienie częsciowo zainicjowanych ziaren, itd.
-powiązane komponenty muszą być dystrybuowane w tym samym module/JARze
-trudniej testować
-itd.

O ile generalnie zgadzam sie z powyższym, bywają sytuacje awaryjne kiedy nie ma innego wyjścia. Nie ma czasu i pieniędzy na zmianę architektury a kopiowanie kodu nigdy nie jest dobrym pomysłem.

Rozwiązaniem jest implementacja interfejsu BeanPostProcessor przez jeden z komponentów i przechwycenie referencji do zależnego bean'a w metodzie postProcessAfterInitialization(). Trzeba pamiętać o zwróceniu w obu metodach instancji przekazanego w argumencie ziarna.


public class StudioService implements BeanPostProcessor {
 
  private UserService userService;
  //...
 
  public Object postProcessAfterInitialization(Object bean, String name) throws BeansException {
   if("userService".equals(name)){
    userService = (UserService)bean;
   }
   return bean;
  }
 
  public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
   return bean;
  }
}


Jeśli komponent będzie występował jako cross-reference więcej niż raz warto napisać, skonfigurować i używać w jego miejsce proxy - obejście zawsze będzie tylko w jednym miejscu.

Linki:
-Spring Dynamic Modules Reference Guide, 5.3.1. Listener and cyclic dependencies
-The Spring Framework - Reference Documentation, 3.7. Container extension points
-architecturerules.org