piątek, 17 grudnia 2010

Spring i wartości *.properties

Ostatnio stanąłem przed koniecznością skonfigurowania nowego projektu w springu. Jedną z rzeczy do zrobienia było czytanie parametrów z plików *.properties. W jednym z pierwszych postów pisałem jak posługiwać się PropertyPlaceholderConfigurer-em, dziś postaram się uzupełnić i spójnie przedstawić tą kwestię w springu.

Pokażę dzisiaj 4 różne podejścia.


Pierwsze będzie pokazywało 'klasyczny', stary, xml-owy sposób.
Wspomniany PropertyPlaceholderConfigurer (nadajmy mu alias 'PPC') jest BeanFactoryPostProcessorem, który przed utworzeniem beanów przegląda konfigurację fabryki springa i podmienia wszystkie stringi w postaci ${'klucz'} na wartość o podanym kluczu. Wartości bierze ze swojej konfiguracji: mogą to być pliki properties, bądź wartości podane 'ręcznie'.
Zatem jeśli do PPC podamy plik proterties z wpisem "user=ryszard" i gdzieś w xml (konfiguracji springa) mamy wpis ${user}, to PPC podmieni go na napis "ryszard". Zauważmy, że mówimy o wartościach w xml-u.

Dla przykładów zdefiniujmy sobie interfejs:
public interface Service {
    String getMessage();
}
Napiszmy sobie implementację interfejsu:
public class XmlStyleService implements Service {

    private String name;
    private String surname;

    public String getMessage() {
        return "Hello " + name + " " + surname;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }
}
do tego konfiguracja w xml-u:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="classpath:config.properties" />
</bean>

<bean id="xmlOldStyleService" class="pl.turo.spring.impl.XmlStyleService">
    <property name="name" value="${imie}" />
    <property name="surname" value="${nazwisko}" />
</bean>
Trzeba jeszcze utworzyć plik properties, ja go sobie nazwę 'config.properties':
imie=ryszard
nazwisko=tur

Co się stało:
  • Zdefniniowałem beana typu PropertyPlaceholderConfigurer-a i podałem mu plik config.properties. Zauważmy, że nie potrzeba mu nadawać id, ponieważ istotne jest tylko to, że pojawi się w konfiguracji - nigdzie wstrzykiwany nie będzie.
  • Zdefniniowałem beana naszego serwisu xmlOldStyleService, gdzie pojawiają się dwie kluczowe linie konfiguracji:
  • <property name="name" value="${imie}" /> <property name="surname" value="${nazwisko}" />
  • Spring przed utworzeniem obiektów, po przeczytaniu i zwalidowaniu konfiguracji, uruchamia post processory, między innymi naszego PropertyPlaceholderConfigurer-a.
  • Ów PPC przelatuje się po całej konfiguracji i szuka wyrażeń ${'klucz'}.
  • Znajdzie nasze dwie linie i szuka w wartości dla podanych kluczy ('imie', 'nazwisko'). Podaliśmy je w pliku config.properties, zatem podmieni te wartości.
  • Z podmienionymi wartościami utworzy beana xmlOldStyleService.
Odpalmy test
@ContextConfiguration(locations={"classpath:/pl/turo/spring/xml-old-style-context.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class OldStyleServiceTests extends TestCase {

    @Autowired
    private Service xmlOldStyle;
 
    @Test
    public void testXmlOldStyle() throws Exception {
        assertEquals("Hello ryszard tur", xmlOldStyle.getMessage());
    }
}
Jak to mówią: "u mnie działa" ;-).
Podsumowanie.
  • Tworzymy PPC, podajemy mu propertisy (tak jak ja z pliku, można i na inne sposoby).
  • Tam gdzie chcemy w xml-u wstrzyknąć wartości z propertisów, piszemy ${'klucz'}, gdzie klucz to klucz naszej wartości.


Podejście drugie również będzie dotykało xml-a i PPC, jednak z uproszczoną składnią. Spójrzmy na konfigurację:
<context:property-placeholder location="classpath:config.properties" />
 
<bean id="xmlNewStyleService" class="pl.turo.spring.impl.XmlStyleService">
    <property name="name" value="${imie}" />
    <property name="surname" value="${nazwisko}" />
</bean>
Namespace 'context' upraszcza i skraca zapis konfiguracji PPC. Działa identycznie jak w przypadku pierwszym. Różnica jaką udało mi się wyłapać, zresztą dość istotna, jest taka że tutaj możemy podać tylko jeden plik *.properties (chociaż jest na to sposób - pokażę w kolejny poście).


Podejście trzecie korzysta z dobrodziejstw SpEL (Spring Expression Language). Spójrzmy:
public class AnnotationStyleService implements Service {

    @Value("#{prop.imie}")
    private String name;
    @Value("#{prop.nazwisko}")
    private String surname;

    public String getMessage() {
        return "Hello " + name + " " + surname;
    }
}
oraz konfiguracja:
<util:properties id="prop" location="classpath:config.properties" />
  
<bean id="annotationStyleService" class="pl.turo.spring.impl.AnnotationStyleService" />
Przeanalizujmy:
  • Korzystając z namespace 'util' tworzymy beana typu java.util.Properties i podajemy mu plik, z którego bean zaczyta sobie dane (mapa klucz->wartość).
  • Należy zwrócić uwagę na to, że tym razem wymagane jest podanie id dla tego bena (w odróżnieniu od PPC). Id posłuży nam w adnotacji @Value na określenie skąd spring ma pobrać wartość do wstrzyknięcia.
  • Zatem zapis @Value("#{prop.imie}") należy rozumieć: z beana o id="prop" (który musi być typu java.util.Properties) weź wartość na kluczu 'imie'.
  • Definiujemy naszego beana bez wstrzykiwania wartości w xml-u.
Odpalmy test:
@ContextConfiguration(locations={"classpath:/pl/turo/spring/annotation-style-context.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class AnnotationServiceTests extends TestCase {

    @Autowired
    private Service annotationStyleService;
 
    @Test
    public void testValueAnnotationStyle() throws Exception {
        assertEquals("Hello ryszard tur", annotationStyleService.getMessage());
    }
}
I jak poprzednio: "u mnie działa".


Podejście czwarte jest wariancją trzeciego. Napisłem, że adnotacja @Value potrzebuje odwołać się do beana typu java.util.Properties. Nic nie stoi zatem na przeszkodzie aby utworzyć tego beana w jeden z wielu sposobów w springu, np tak:
<bean id="prop" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="locations">
        <list>
            <value>classpath:config.properties</value>
        </list>
    </property>
</bean>
 
<bean id="annotationStyleService" class="pl.turo.spring.impl.AnnotationStyleService" />
Skorzystałem tutaj z dostarczonej przez springa fabryki beanów oczekiwanego przez nas typu (java.util.Properties). Jak poprzednio nadałem temu beanowi id (zgodne z tym co mamy w adnotacji @Value).
Zauważmy, że tutaj możemy podać listę wielu plików properties. Reszta działa jak w przypadku trzecim.
(Opis jak działa fabryka springa można znaleźć m.in. w moim wcześniejszym poście.)

Następnym razem opiszę kilka smaczków dotyczących obsługi propertisów w springu.
Stąd można pobrać projekt, w którym testowałem opisywane funkcjonalności.

2 komentarze:

  1. plus kozak opcja - podmiana wartosci z plikow .properties przez nadpisywanie ich zmiennych systemowymi lub parametrami wywolania mavena.

    OdpowiedzUsuń
  2. Wydaje mi się, że mówimy o dwóch różnych rzeczach: ja piszę o pracy z propertisami w springu (runtime) a Ty piszesz o filtrowaniu zasobów przez mavena w czasie budowania projektu. Można zrobić nawet więcej niż nadpisywanie propertisów wartościami ze zmiennych systemowych: a czemu nie pobierać ich z jakiegoś serwera konfiguracyjnego? Można by mnożyć pomysły ;)
    Pozdrawiam.

    OdpowiedzUsuń