Spring z założenia integruje się z wybranymi elementami specyfikacji J2EE takimi jak np. @PostConstruct oraz @PreDestroy. Jednak pomimo istnienia takiej ogólnodostępnej specyfikacji spotyka się osoby, które nie chcą “zabrudzać” kodu adnotacjami wchodzącymi w jej skład. Wynika to z myślenia, że kod domeny musi być “czysty”!

W tym wpisie skupimy się na cyklu życia zarejestrowanego beana, a dokładniej na inicjalizacji oraz usuwaniu go z kontekstu Springa. Sprawdzimy w jaki sposób możemy zapiąć się z naszym kodem na lifecycle callbacks oraz czy w tym kontekście można zadowolić wcześniej wspomniane osoby.

I sposób - mocne związanie ze Springiem

Pierwsze rozwiązanie mocno splątuje nasz serwis z frameworkiem. Spring daje nam do dyspozycji dwa interfejsy - InitializingBean oraz DisposableBean. Jeśli nasza klasa domenowa je zaimplementuje oraz zarejestrujemy jej instancję jako bean to przy jego tworzeniu wykona się kod znajdujący się w metodzie afterPropertiesSet, a przy niszczeniu z metody destroy.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class InitDestroy_1_InterfacesBean implements DisposableBean, InitializingBean {

  @Override
  public void afterPropertiesSet() {
    System.out.println("initializing using interfaces...");
  }

  @Override
  public void destroy() {
    System.out.println("destroying using interfaces...");
  }

}

II sposób - splątanie ze specyfikacją J2EE

Zamiast mocnego couplingu do Springa mamy do dyspozycji inne wyjście. Możemy skorzystać z adnotacji wchodzących w skład specyfikacji J2EE. Wystarczy interesujące nas metody oznaczyć poprzez @PostConstruct i @PreDestroy, a Spring poradzi sobie w myśl “uzupełniania się” z ogólnoprzyjętymi standardami.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class InitDestroy_2_AnnotationsBean {

  @PostConstruct
  public void init() {
    System.out.println("initializing using annotations...");
  }

  @PreDestroy
  public void destroy() {
    System.out.println("destroying using annotations...");
  }

}

III sposób - “czystość” ponad miarę

Ostatnie rozwiązanie powinno zadowolić osoby najbardziej wybredne. Tutaj definicję callbacków cyklu życia beana przerzucamy na klasę infrastrukturalną. Klasa domenowa pozostaje nietknięta niczym. Po prostu ma dwie metody wyglądające jak najzwyklejsze metody z Javy. Natomiast podczas rejestrowania beana, przy pomocy @Bean, wskazujemy metody, które mają się wywołać po inicjalizacji oraz przed usunięciem beana.

1
2
3
4
5
6
7
8
9
10
11
public class InitDestroy_3_MethodsBean {

  public void init() {
    System.out.println("initializing using methods...");
  }

  public void destroy() {
    System.out.println("destroying using methods...");
  }

}
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 InitDestroyConfig {

  @Bean
  InitDestroy_1_InterfacesBean initDestroy1InterfacesBean() {
    return new InitDestroy_1_InterfacesBean();
  }

  @Bean
  InitDestroy_2_AnnotationsBean initDestroy2AnnotationsBean() {
    return new InitDestroy_2_AnnotationsBean();
  }

  @Bean(
      initMethod = "init",
      destroyMethod = "destroy"
  )
  InitDestroy_3_MethodsBean initDestroy3MethodsBean() {
    return new InitDestroy_3_MethodsBean();
  }

}

Podsumowanie

Utwórzymy sobie test, który zarejestruje nam wyżej wymienione beany. Następnie go uruchommy, aby zobaczyć, że każde rozwiązanie działa w ten sam sposób.

1
2
3
4
5
6
7
8
9
10
11
@SpringBootTest(
    classes = InitDestroyConfig.class
)
class InitDestroyConfigTest {

  @Test
  void testInitDestroyMethods() {

  }

}
1
2
3
4
5
6
initializing using interfaces...
initializing using annotations...
initializing using methods...
destroying using methods...
destroying using annotations...
destroying using interfaces...

Jak widać na tym przykładzie Spring ma mechanizmy, które pozwalają nam korzystać z jego dobrodziejstw nie zrastając się z nim w całości. Dobrze o nich wiedzieć i być może nawet stosować. Jednak to nie adnotacje sprawiają, że nasz kod wygląda jak spaghetti. W kontekście adnotacji warto wspomnieć słowa Sławka Sobótki:

Adnotacje są jak łupież. Co prawda nie wyglądają zbyt dobrze, ale Cię nie zabiją.

Ta myśl plus to, że trzeba pisać małe programy, które da się szybko przepisać sprawia, że mocna zależność do frameworka nie jest aż tak wielkim problemem. Nawet wręcz obchodzenie takiego couplingu może nam przyspożyć więcej problemów niż korzyści…