niedziela, 26 września 2010

Za kulisami fabryki springa cz.2

O instancjonowaniu beanów.

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.

Brak komentarzy:

Prześlij komentarz