niedziela, 17 października 2010

Stawiamy springa na nogi, tym razem adnotacyjne

W poprzednich postach rozpisywałem się o tworzeniu obiektów przez Springa, o tym co po drodze możemy zrobić. Do tej pory na blogu królował xml - większość rzeczy, które pokazałem konfigurowałem właśnie w xml-u. Teraz chciałbym pokazać alternatywne podejście. Od javy 5 mamy w języku java dostępne adnotacje - spring również je wspiera.

Do tej pory pokazałem wykorzystanie adnotacji :

@Configuration - nad klasą oznacza, że dana klasa zostanie przez springa wykorzystana do utworzenia beanów w kodzie.
@Import - powiązany z @Configuration, pozwala wstrzykiwać beany utworzone w innej klasie oznaczonej @Configuration
@Bean - w kontekście @Configuration nad metodą oznacza, że obiekt zwracany będzie beanem springa
@Autowired - 'auto wstrzykiwanie', o tym szerzej napiszę
@PostConstruct - adnotacja cyklu życia beana. Metoda tak zaadnotowana zostanie uruchomiona po utworzeniu beana.
@PreDestroy - adnotacja cyklu życia beana. Metoda tak zaadnotowana zostanie uruchomiona przed usunięciem beana z fabryki springa
@Service - niżej napiszę szerzej
@Value - wstrzykiwanie wartości z PropertyPlaceholderConfigurera
@Scope - określa zasięg widoczności beana.

Zacznijmy od adnotacji związanych z definiowaniem beanów. Pierwszym krokiem jaki należy wykonać aby móc korzystać z adnotacji jest kawałek xml-a, w którym powiemy springowi, że beany mamy adnotacyjnie skonfigurowane :
<context:component-scan base-package="pl.turo.spring" />
Czytaj więcej o tej konfiguracji.

Teraz możemy nad klasami znajdującymi się w podanej lokalizacji skorzystać z @Component lub adnotacji dziedziczących po niej, Przykład :
@Component
public class Foo {
}
Spring widząc taką adnotację utworzy beana i nada mu id 'foo'. Taki bean może być wstrzykiwany adnotacyjnie czy też xml-owo do innych beanów.

Po adnotacji @Component "dziedziczą" :
* @Service
* @Repository
* @Controller (o nim więcej napiszę przy okazji spring MVC)

Możemy zaadnotować klasę którąkolwiek z tych adnotacji i spring utworzy odpowiedniego beana. Po cóż zatem mielibyśmy korzystać np z @Service zamiast poprostu z @Component ? Po co zostały stworzone takie adnotacje ? Osobne adnotacje określają osobne stereotypy. Pozwalają na podział wielowarstwowy aplikacji. Wyobraźmy sobie, że korzystamy z AOP (o czym jeszcze będę pisał) i chcemy wszystkie wywołania wszystkich metod serwisów logować. Jeśli konsekwentnie korzystalibyśmy z odpowiednich stereotypów to sprawę mamy mocno uproszczoną - łatwo będzie nam zdefiniować odpowiedni pointcut. O AOP będę jeszcze pisał i wrócę do tego problemu.
Wiemy już jak definiować beany adnotacjami. Kolej na wstrzykiwanie zależności.
@Service
public class Bar {
}

@Service
public class Foo {
    @Autowired
    private Bar bar;
}
Spring wstrzyknie referencję odpowiedniego beana. Innymi słowy pobierając z fabryki springa beana 'foo' mamy pewność że pole bar będzie wstrzyknięte. @Autowired jest adnotacją springową, zamiast niej możemy użyć @Resource (z JSR-250). Zatem możemy mieć tak :
@Service 
public class Foo {
    @Resource
    private Bar bar;
}
Jaka zatem jest różnica ? @Autowired stara się wstrzyknąć beana na podstawie typu (spring szukając kandydata do wstrzyknięcia szuka po typie), natomiast @Resource szuka po nazwie zmiennej.

W obu przypadkach jeśli spring nie znajdzie kandydata do wstrzyknięcia to rzuci wyjątkiem i nie uruchomi się - można to przekonfigurować podając wartość :
@Service 
public class Foo {
   @Autowired (required=false)
   private Bar bar = new DefaultBarImplementation();
}
Jeśli spring nie znajdzie pasującego do wstrzyknięcia beana to nie zrobi nic. Jest to też prosty sposób na na podanie domyślnych implemetacji.
A co jeśli spring znajdzie więcej niż 1 kandydata do wstrzyknięcia ? Również rzuci wyjątkiem i nie uruchomi się. Można natomiast wstrzyknąć kolekcję beanów :

@Service 
public class Foo {
   @Autowired
   private List<Bar> bars;
}
Przy takiej konfiguracji spring utworzy i wstrzyknie listę, następnie doda do niej wszystkie pasujące typem beany.

Kolejny przypadek jest taki, że chcemy wstrzyknąć explicite jakiegoś beana (po nazwie), możemy zrobić to tak :
@Service 
public class Foo {
   @Autowired 
   @Qualifier("barBean")
   private Bar bar;
}

@Service("barBean")
public class Bar {}
Nad klasą Bar podajemy id = "barBean". oraz wstrzykując referencję do niego w klasie Foo podajemy również to id korzystając z adnotacji @Qualifier.

Definiując beany typu Bar możemy określić, że któryś ma być brany jako pierwszy do wstrzykiwania:
@Service
public class Bar {}

@Service
@Primary
public class SubBar extends Bar {}

@Service 
public class Foo {
   @Autowired 
   private Bar bar;
} 
Teraz spring wstrzyknie beana 'subBar' do beana 'foo', nie rzuci wyjątkiem mając więcej niż jeden bean jako kandydata do wstrzyknięcia, wybierze tego z adnotacją @Primary.

Idźmy dalej. @Autowired i @Resource pozwalają na adnotowanie metod :
@Service
public class Foo {
   private Bar bar;

   @Autowired
   public void inject (Bar bar) {
       this.bar = bar;
   }
}
W tym przypadku spring będzie wstrzykiwał beany korzystając z metody inject (uruchomi ją w fazie inicjalizacji beanów). Będzie wstrzykiwał wszystkie parametry. @Autowired można również użyć nad konstruktorem (@Resource już nie).

Skoro spring wspiera konfigurację zarówno adnotacyjną, jak i xml-ową to którą wybrać ? Istnieje kilka zasad pomagających rozstrzygnąć czego użyć :
* jeśli konfigurujemy beany, których źródeł nie mamy to musimy skorzystać z xml'a
* adnotacje wydają się lepsze dla często zmieniających się elementów
* można w jednej fabryce mieszać te podejścia (pytanie czy jest to dobry pomysł ?)
* pamiętajmy, że xml-a można łatwo podmienić przy testach

Osobiście uważam, że są pewne rzeczy, które lepiej konfigurować w xml, inne adnotacyjnie. Pracując w projektach z wykorzystaniem springa mam takie spostrzeżenie, że xml wymusza rozumienie tego co się robi, a adnotacje działają 'automagicznie'. Widziałem programistów, którzy pracując kilka miesięcy i korzystając z adnotacji nie wiedzą dlaczego to działa ani jak to działa. Widzą tylko 2 rzeczy :
1. Nad klasą napisz @Service,
2. Jak chcesz skorzystać z innej klasy to dodaj pole i napisz @Autowired.

Jeśli tylko tyle programista wie o springu, to niestety moim zdaniem jest to niebezpieczna sytuacja. Myślę, że dotyczy to nie tylko znajomości springa, generalnie jeśli programista nie wie dlaczego coś działa i dlaczego działa właśnie tak a nie inaczej mamy poważny problem. Co jeśli trzeba będzie zmienić domyślne zachowanie ?

Reasumując myślę, że adnotacje są świetne w kilku przypadkach (dla mnie to np : kontrolery MVC, aspekty, transakcje), lecz preferuję xml-a. Jakie jest Wasze zdanie ?
To wszystko co chciałem przekazać w temacie wstrzykiwania beanów adnotacyjnie. Następnym razem napiszę kilka słów o AOP.

3 komentarze:

  1. Wiele zrozumiałem dzieki temu postowi:).

    Tylko brakuje mi czegoś o @Repository który chyba również dziedziczy po @Component.

    OdpowiedzUsuń
  2. ta strona mi sie wiesza na czterordzeniowym procesorze i 8 gb pamieci, musialem skopiowac jej treść do notatnika bo w przegladarce nie dalo sie jej przegladac

    OdpowiedzUsuń
  3. Świetne wyjaśnienie ;) Jasne i klarowne ;)

    OdpowiedzUsuń