Spring tworząc obiekty korzysta z refleksji i oczekuje istnienia konstruktora bezparametrowego. Widoczność nie znaczenia. Co w przypadku gdy nie mamy możliwości bądź nie chcemy takiego konstruktora udostępniać ? Istnieją inne metody.
Możemy utworzyć statyczną metodę fabrykującą. Spójrzmy na klasę i jej konfigurację :
public class ExampleService implements Service { private Repository repo; public ExampleService(Repository repository) { super(); this.repo = repository; } private static ExampleService newInstance(Repository repository) { return new ExampleService(repository); } }
oraz konfiguracja :
<bean id="service" class="pl.turo.spring.service.ExampleService" factory-method="newInstance"> <constructor-arg ref="repository" /> </bean> <bean id="repository" class="pl.turo.spring.repository.ExampleRepository" />Spring tworząc beana "service" nie będzie szukał konstruktora z parametrem zgodnym typem z Repository, ale na klasie ExampleService uruchomi metodę newInstance i jej w parametrze przekaże referencję do beana "repository". Jak widać metoda ta może być prywatna. Jeśli chcielibyśmy mieć taką metodę w innej klasie to nie ma problemu :
public class ServiceFactory { private Repository repository; public ExampleService newService() { return new ExampleService(repository); } public void setRepository(Repository repository) { this.repository = repository; } }oraz konfiguracja :
<bean id="serviceFactory" class="pl.turo.spring.service.ServiceFactory"> <property name="repository" ref="repository" /> </bean> <bean id="service" class="pl.turo.spring.service.ExampleService" factory-method="newService" factory-bean="serviceFactory" /> <bean id="repository" class="pl.turo.spring.repository.ExampleRepository" />Tym razem spring tworząc beana "service" oddeleguje utworzenie obiektu do bena-fabryki ("serviceFactory") i na nim wywoła metodę newService.
Zauważmy, że przeciwnie do pierwszego przykładu - tutaj tworzymy beana będącego fabryką, więc metoda nie jest już statyczna.
Kolejną możliwością jest zaimplementowanie interefejsu FactoryBean. Beany takie są automatycznie wykrywane przez springa (BeanFactoryPostProcessor).
Przykład :
public class RepositoryFactory implements FactoryBean<Repository> { private boolean singleton; private Repository instance; public Repository getObject() throws Exception { if (!singleton || instance == null) { instance = new ExampleRepository(); } return instance; } public Class getObjectType() { return Repository.class; } public boolean isSingleton() { return singleton; } public void setSingleton(boolean singleton) { this.singleton = singleton; } }oraz konfiguracja :
<bean id="repositoryFactory" class="pl.turo.spring.repository.RepositoryFactory"> <property name="singleton" value="false" /> </bean> <bean id="service" class="pl.turo.spring.service.ExampleService"> <constructor-arg ref="repositoryFactory" /> </bean>Widzimy konfigurację fabryki. Bean tej fabryki jest użyty w miejscu, gdzie powinien pojawić się bean 'repository' i spring domyśli się, że należy uruchomić metodę getObject().
Ponieważ fabryka to najzwyklejszy w świecie bean, to można jej wstrzykiwać wartości/beany. Jedną z częściej wykorzystywanych fabryk w springu jest fabryka sesji hibernate :
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="packagesToScan" value="pl.turo.spring" /> <property name="schemaUpdate" value="true" /> <property name="hibernateProperties"> <props> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop> </props> </property> <!-- dalsza konfiguracja --> </bean>
To nie koniec możliwości. Spring 3 umożliwia tworzenie konfiguracji w kodzie javy - wystarczy dodać adnotację @Configuration do beana. Przykład :
@Configuration public class RepositoryConfiguration { @Bean public Repository repository() throws Exception { RepositoryFactory repositoryFactory = new RepositoryFactory(); repositoryFactory.setSingleton(true); return repositoryFactory.getObject(); } }oraz :
@Configuration @Import({ RepositoryConfiguration.class }) public class ServiceConfiguration { @Autowired private Repository repository; @Bean public Service service () throws Exception { return new ExampleService(repository); } }i konieczny xml :
<context:annotation-config /> <bean class="pl.turo.spring.configuration.RepositoryConfiguration" /> <bean class="pl.turo.spring.configuration.ServiceConfiguration" />Pokazałem jak utworzyć więcej niż jeden bean konfiguracyjny i jak z jednego wyciągnąć beany aby wstrzyknąć drugiemu (repository wstrzykujemy do service).
W temacie instancjonowania obiektów należy jeszcze wspomnieć o metodach zwrotnych cyklu życia beana. Możemy chcieć aby spring uruchomił metodę beana zaraz po jego utworzeniu i wstrzyknięciu zależności. Możemy to osiągnąć na 3 sposoby :
- adnotacją @PostConstruct z JSR-250 (metoda musi być bezparametrowa - to jedyne wymaganie)
- atrybutem init-method w tagu bean (metoda musi być bezparametrowa - to jedyne wymaganie)
- implementując interfejs InitalizingBean (metoda afterPropertiesSet())
Podobnie możemy chcieć wykonać jakąś akcję przed zniszczeniem beana (dokładniej tuż przed odpięciem beana od fabryki springa)
- adnotacją @PreDestroy
- atrybutem destroy-method w tagu bean
- implementując interfejs DisposableBean (metod destroy())
To na tyle w tamacie instancjonowania obiektów.