Cykl życia springa dzieli się na 3 etapy :
1. initialization
2. use
3. destruction
Ad 1.
W tej fazie spring :
1. parsuje wszystkie xml-e
2. ładuje definicje beanów do fabryki (każdy bean pod unikalnym id)
3. uruchamiane są BeanFactoryPostProcessor-y - mogą one zmieniać definicję beanów.
Przykładem takiego postprocessora jest PropertyPlaceholderConfigurer, którym zastępowane są wyrażenia ${nazwa_wartości} wartościami (np zaczytanymi z plików .properties lub wstrzykniętymi). Uruchamiane są zanim zostaną utworzone obiekty. Można napisać własny BeanPostProcessor.
Przykład użycia PropertyPlaceholderConfigurer-a :
Zdefiniowałem dwa bean-y. Bean-owi 'dataSource' w fazie inizjalicacji zostaną ustawione wartości wczytane z plików .properties. Spring czytając definicje bean-ów automatycznie wykrywa beana implementującego interfejs BeanFactoryPostProcessor i go uruchomi, zatem wystarczy że pojawi się definicja PropertyPlaceholderConfigurer-a. Czy można mieć kilka definicji PropertyPlaceholderConfigurer-a ? Pewnie, że można, tylko będąc świadomym jak BeanFactoryPostProcessor-y działają musimy wiedzieć, że taka konfiguracja poprawna nie będzie :
Korzystając ze springa 3 możemy użyć namepsace 'context' i nieco skrócić konfigurację. Zamiast :
Spring 3 daje nam jeszcze inne możliwości. Pokazałem jak można korzystać z BeanFactoryPostProcessor-ów w xml-u, spójrzmy jak to można zrobić adnotacjami :
Co ciekawe password będzie miał wartość 'tajne' - jak widać nowe_hasło nie nadpisuje nam wartości zaczytanej z pliku.
Kiedy spring uruchomi już wszystkie post processory - wtedy dopiero będzie instancjonował beany. Domyślnie każdy bean jest instancjonoawny zachłannie i jest singletonem. Oba zachowania można przekonfigurować.
Instancjonowanie można zmienić atrybutem lazy-init taga bean podając wartość true. Spring używa refleksji do tworzenia obiektów, więc dla singletonów wydaje się dobrym pomysłem instancjonowanie zachłanne.
Jeśli potrzebujemy innego zasięgu, to mamy do dyspozycji :
* prototype - spring utworzy zawsze nowy obiekt przy każdym wstrzykiwaniu tego beana. Innymi słowy jeśli w kilku miejscach będziemy wstrzykiwać beana to każdorazowo otrzymamy inną instancję
* request - chodzi o HTTP request.
* session - chodzi o HTTP session.
* można również zdefiniować swój własny scope.
Tag bean posiada strybut scope, gdzie podajemy zasięg, analogicznie jest adnotacja @Scope. Załóżmy, że przedefiniowaliśmy beana PropertyBean tak, aby miał zasięg sesji. Jeśli wstrzykiwalibyśmy go do singletonowego beana pojawi się nam problem : otóż spring w momencie instancjonowania obiektów nie wie nic o sesji http i nie będzie umiał takiego PropertyBean-a nigdzie wstrzyknąć. Istnieje tutaj możliwość skorzystania z proxy - zamiast starać się utworzyć obiekt sesyjny, utwórzmy do niego proxy. Możemy to zrobić anotacyjnie bądź xml-owo. Spójrzmy :
W xml-u wygląda to tak :
To tyle na dziś.
1. initialization
2. use
3. destruction
Ad 1.
W tej fazie spring :
1. parsuje wszystkie xml-e
2. ładuje definicje beanów do fabryki (każdy bean pod unikalnym id)
3. uruchamiane są BeanFactoryPostProcessor-y - mogą one zmieniać definicję beanów.
Przykładem takiego postprocessora jest PropertyPlaceholderConfigurer, którym zastępowane są wyrażenia ${nazwa_wartości} wartościami (np zaczytanymi z plików .properties lub wstrzykniętymi). Uruchamiane są zanim zostaną utworzone obiekty. Można napisać własny BeanPostProcessor.
Przykład użycia PropertyPlaceholderConfigurer-a :
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${driver}" /> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> </bean> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <util:list> <value>/datasource.properties</value> <value>/user.properties</value> </util:list> </property> </bean>datasource.properties :
driver=oracle.jdbc.driver.OracleDriver url=jdbc:oracle:thin:@localhost:1521:BAZAoraz user.properties :
username=slawek password=tajne
Zdefiniowałem dwa bean-y. Bean-owi 'dataSource' w fazie inizjalicacji zostaną ustawione wartości wczytane z plików .properties. Spring czytając definicje bean-ów automatycznie wykrywa beana implementującego interfejs BeanFactoryPostProcessor i go uruchomi, zatem wystarczy że pojawi się definicja PropertyPlaceholderConfigurer-a. Czy można mieć kilka definicji PropertyPlaceholderConfigurer-a ? Pewnie, że można, tylko będąc świadomym jak BeanFactoryPostProcessor-y działają musimy wiedzieć, że taka konfiguracja poprawna nie będzie :
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${driver}" /> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> </bean> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="/user.properties" /> </bean> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="/datasource.properties" /> </bean>a dlaczego ? Otóż pierwszy PropertyPlaceholderConfigurer nie ma wczytanych wszystkich wartości (podaliśmy mu tylko to, co jest w pliku /user.properties). Zatem widząc taką wartość "${driver}" w definicji beana "dataSource" rozpoznaje jako ciąg, który należy podmienić i szuka w swojej mapie wartości o takim kluczu. Nie znajduje jej (ponieważ nie ma takiej wartości w /user.properties, jest natomiast w dasasource.properties) i skutkuje to wyjątkiem :
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'dataSource' defined in class path resource [META-INF/spring/app-context.xml]: Could not resolve placeholder 'driver'.Rozwiązaniem problemu jest atrybut ignoreUnresolvablePlaceholders. W kolejnym poście szerzej opisałem działanie PropertyPlaceholderConfigurer'a.
Korzystając ze springa 3 możemy użyć namepsace 'context' i nieco skrócić konfigurację. Zamiast :
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="/datasource.properties" /> </bean>możemy mieć :
<context:property-placeholder location="/datasource.properties"/>te zapisy są sobie równoważne.
Spring 3 daje nam jeszcze inne możliwości. Pokazałem jak można korzystać z BeanFactoryPostProcessor-ów w xml-u, spójrzmy jak to można zrobić adnotacjami :
@Service public class PropertyBean { @Value(value = "#{prop.username}") private String user; @Value(value = "#{prop.password}") private String password; public String getUser() { return user; } public String getPassword() { return password; } }zakładając, że w xml - u pojawi się bean o id 'prop' który będzie typu java.util.Properties i będzie posiadał wartość o kluczu 'username' :
<util:properties id="prop" location="/user.properties"> <prop key="password">nowe_hasło</prop> </util:properties>oraz powiemy springowi że mamy adnotacyjnie skonfigurowane bean-y :
<context:component-scan base-package="pl.turo.spring" />O adnotacjach jeszcze wspomnę.
Co ciekawe password będzie miał wartość 'tajne' - jak widać nowe_hasło nie nadpisuje nam wartości zaczytanej z pliku.
Kiedy spring uruchomi już wszystkie post processory - wtedy dopiero będzie instancjonował beany. Domyślnie każdy bean jest instancjonoawny zachłannie i jest singletonem. Oba zachowania można przekonfigurować.
Instancjonowanie można zmienić atrybutem lazy-init taga bean podając wartość true. Spring używa refleksji do tworzenia obiektów, więc dla singletonów wydaje się dobrym pomysłem instancjonowanie zachłanne.
Jeśli potrzebujemy innego zasięgu, to mamy do dyspozycji :
* prototype - spring utworzy zawsze nowy obiekt przy każdym wstrzykiwaniu tego beana. Innymi słowy jeśli w kilku miejscach będziemy wstrzykiwać beana to każdorazowo otrzymamy inną instancję
* request - chodzi o HTTP request.
* session - chodzi o HTTP session.
* można również zdefiniować swój własny scope.
Tag bean posiada strybut scope, gdzie podajemy zasięg, analogicznie jest adnotacja @Scope. Załóżmy, że przedefiniowaliśmy beana PropertyBean tak, aby miał zasięg sesji. Jeśli wstrzykiwalibyśmy go do singletonowego beana pojawi się nam problem : otóż spring w momencie instancjonowania obiektów nie wie nic o sesji http i nie będzie umiał takiego PropertyBean-a nigdzie wstrzyknąć. Istnieje tutaj możliwość skorzystania z proxy - zamiast starać się utworzyć obiekt sesyjny, utwórzmy do niego proxy. Możemy to zrobić anotacyjnie bądź xml-owo. Spójrzmy :
@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS, value="session")możemy również użyć ScopedProxyMode.INTERFACES - różnica między jednym a drugim jest istotna. Pierwszy korzysta z CGLIB tworząc proxy, drugi używa dynamic proxy z JDK.
W xml-u wygląda to tak :
<bean id="repository" class="pl.turo.spring.repository.ExampleRepository" scope="session"> <aop:scoped-proxy /> </bean>i użyty tu jest CGLIB. Proxy to będzie odpowiedzialne za dostęp do odpowiedniego obiektu pobranego z sesji http. Czyli nasz singletonowy bean posiada referencję do proxy, które to potrafi delegować wywołania metod do odpowiedniego beana. To naprawdę działa.
To tyle na dziś.
Brak komentarzy:
Prześlij komentarz