sobota, 23 października 2010

Warsjava 2010

Miałem dziś możliwość/przyjemność uczestniczyć w konferencji Warsjava 2010. Wielkie dzięki organizatorom. Było 6 konferencji, ciekawych i ciekawszych. Na pewno poszperam w Clojure, dzięki Marcinowi Rzewuckiemu - dzięki. Pomimo małych wpadek w "egit i przyjaciele" zachęcają do co najmniej rozważenia gita przy kolejnym wyborze systemu kontroli wersji. Generalnie konferencję oceniam jako udaną. Poszerzyła moje horyzonty i zainspirowała do własnych poszukiwań i rozwoju.

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.

sobota, 9 października 2010

Smaczki konfiguracji xml-owej

Dziedziczenie definicji bean-ów.
W xml-u mamy możliwość zdefiniowania 'dziedziczenia' beanów. Jeśli dodamy atrybut parent do taga bean mówimy springowi : "weź wszystkie propertiesy z parenta i wstrzyknij i mnie".
<bean id="commonProperty" class="" >
 <property name="url" value="http://slawekturowicz.blogspot.com" />
 <property name="author" value="slawek" />
</bean>

<bean id="exampleInteritanceBean" class="" parent="commonProperty" >
 <property name="author" value="maciek" />
</bean>
W tym przykładzie "exampleInteritanceBean" będzie miał wstrzyknięte dwa propertisy :
  • "url" o wartości : "http://slawekturowicz.blogspot.com"
  • "author" o wartości : "maciek".
Zauważmy, że można nadpisywać wartości. W powyższym przykładzie spring utworzy dwa beany "commonProperty" oraz "exampleInteritanceBean". Możemy nie chcieć tworzyć instancji parenta - w momencie, gdy służy nam do wyekstrachowania zbioru wartości, możemy dodać atrybut abstract="true" w tagu bean.
<bean id="commonProperty" class="" abstract="true" >
 <property name="url" value="http://slawekturowicz.blogspot.com" />
 <property name="author" value="slawek" />
</bean>

Konwersja wstrzykiwanych wartości.
Czasem chcemy wstrzyknąć explicite jakąś wartość podaną w xml-u (a nie referencję do innego beana). W xml-u możemy wprowadzić tylko tekst, co zatem z konwersją ? Spring udostępnia zbiór predefiniowanych konwerterów i po typie wartości jaką chcemy ustawić próbuje konwertować tekst z xml-a. Przykład :
<bean id="postCodeValidator" class="pl.turo.spring.validator.PostCodeValidator" >
 <property name="pattern" value="[0-9][0-9]-[0-9][0-9][0-9]" />
</bean>
i klasa :
puclic class PostCodeValidator {
 private Pattern pattern;
 
 public setPattern(Pattern pattern) {
  this.pattern = pattern;
 }
}
Co tu się dzieje ? - wstrzykujemy do property : "pattern" wartość (tekst) : "[0-9][0-9]-[0-9][0-9][0-9]". Spring widzi, że oczekiwanym typem jest Pattern i szuka odpowidniego Property Editora. Do klasy Pattern posiada "out-of-the-box", więc go używa i stara się dokonać konwersji. Fajne, nie ?

W pacieke org.springframework.beans.propertyeditor możemy znaleźć je wszystkie, oto podstawowa lista :
  • ­NumberEditor
  • ­BooleanEditor
  • ­DateEditor
  • ­ResourceEditor
  • ­PropertiesEditor
  • ­LocaleEditor
Można zaimplementować swój własny PropertyEditor, wystarczy podziedziczyć po PropertyEditorSupport i zaimplementować dwie metody :
  1. setAsText(String) do konwersji ze stringa
  2. getAsText() do sformatowania wartości (ta metoda jest opcjonalna).
public class PatternEditor extends PropertyEditorSupport {

 public void setAsText(String text) {
  setValue(text != null ? Pattern.compile(text) : null);
  }
   
 public String getAsText() {
  Pattern value = (Pattern) getValue();
  return (value != null ? value.pattern() : "");
 }
}
Należy jeszcze zarejestrować nasz edytor w springu. Można to zrobić np tak:
 <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
  <property name="customEditors">
   <map>
    <entry key="java.util.regex.Pattern">
     <bean class="pl.turo.spring.editor.PatternEditor" />
    </entry>
   </map>
  </property>
 </bean>
Nazewnictwo beanów.
Każdy bean w fabryce springa posiada unikalny identyfikator id. Jeśli explicite go nie nadamy to spring domyślnie nada id takie jak nazwa klasy z pierwszą literą pomniejszoną. Dzięki id możemy naszego beana wstrzykiwać innym beanom oraz możemy z fabryki springa pobrać jego instancję.
Atrybut id posiada kilka wad :
  • może mieć tylko jedną wartość
  • nie można w nim umieścić pewnych znaków np '/' , ';' itd.
Jeśli potrzebujemy ominąć te restrykcje to możemy użyć atrybutu name zamiast id (albo wraz z id)
<bean name="dataSource, dataSource/primary" .../> 

Wadą konfiguracji xml-owej jest to, że potrafi bardzo się rozrosnąć. Spring wychodzi temu naprzeciw dodając w wersji 3 rozszerzenia upraszczające wiele z konfiguracji. W poprzednich postach już pojawiły się :
  • namespace util:
<util:list> 
 <value>/datasource.properties</value> 
 <value>/user.properties</value> 
</util:list>
Analogicznie mamy również util:map oraz util:set do deklarowania map oraz zbiorów.
Możemy również wstrzykiwać wartości publicznych pól statyczny w klasach :
<bean class="pl.turo.spring.ExampleService">
 <property name="value">
   <util:constant static-field="pl.turo.spring.ExampleRepository.STATIC_VALUE"/>
  </property>
</bean>

  • namespace context:
Odnosi się do konfiguracji fabryki springa, np :
<context:property-placeholder location="/datasource.properties"/>
<context:component-scan base-package="pl.turo.spring" />

  • namespace aop
<aop:scoped-proxy />
O możliwościach Spring AOP i samej konfiguracji będę szerzej pisał w dalszych postach.

  • p-namespace :
Aby wstrzyknąć zależność nie musimy deklarować taga <property> a zamiast tego mamy atrybut w tagu bean, po 'staremu' :
</property>
<bean name="example" class="pl.turo.spring.Repository">
        <property name="dataSource" ref="dataSource"/>
</bean>
korzystając z p-namespace :
<bean name="example" class="pl.turo.spring.Repository" 
 p:dataSource-ref="dataSource"/>
To tyle odnośnie xml-a, następnym razem opiszę jak konfigurować springa adnotacyjne.