MokaByte 89 - 8bre 2004 
Spring e l'inversion of control in un esempio pratico: Jetspeed2
di
Massimiliano Dessì

intro

Introduzione
Molti progetti Java di livello Enterprise hanno strutture che normalmente coinvolgono vari framework per potersi avvalere delle loro caratteristiche, si pensi al caso più lampante di un portale, facente parte di una architettura di tipo SOA (*), con un ESB (**) come dorsale fra le varie applicazioni, servizi e risorse.
In questo scenario il portale, che è visto come applicazione, ha il compito di aggregare le informazioni recuperate attraverso l' ESB. Queste informazioni sono dati recuperati da sistemi legacy o da altre fonti eterogenee attraverso dei web-services.
Questa mole di informazioni, ha necessità di essere trattata adeguatamente prima di essere aggregata in maniera consona all' utente che deve poter avere le informazioni che gli sono necessarie.
Il portale ancora prima di iniziare il lavoro di elaborazione, avrà bisogno localmente, dei servizi offerti da vari framework.
Si pensi al recupero delle credenziali dell' utente, delle sue preferenze nella visualizzazione delle informazioni, queste poche operazioni hanno bisogno dei dati recuperati da un database e del supporto dei servizi di presentazione, e di logica applicativa che permettono di fornire all' utente finale ciò che ha richiesto.

 

Soluzioni
Dal punto di vista architetturale, è necessario usufruire di un framework che fornisca dei servizi (accesso a database , O/R mapping, JNDI , LDAP , template service e altri ), al quale eventualmente aggiungerne di propri.
Naturalmente è necessario anche qualcuno che aggreghi questi servizi e fornisca un unico punto di accesso dal quale consultarli, questa aggregazione può produrre codice molto complesso.
Prendendo come esempio un portale che faccia uso delle portlet , ad esempio Jetspeed-1, questo lavoro era supportato dal framework Turbine, il quale forniva i servizi necessari a Jetspeed.
Un problema che si incontrava nell' aggiungere dei servizi personalizzati, era la necessità di implementare numerosi metodi per consentire l' integrazione all' interno di Turbine.
Questo problema illustrato è comune anche ad altre tipologie applicative.
Essendo l' integrazione dei servizi, un problema ricorrente, si è trovata una nuova soluzione progettuale al problema, mediante un pattern, chiamato Inversion of Control (IoC) o anche Dependency Injection, in antitesi con quello più classico del Service Locator.

 

Inversion of Control
Il nome al pattern è stato dato nel 2004 da Martin Fowler, vediamo il perchè di questa nome.
L' IoC permette di descrivere come gli oggetti devono essere valorizzati e con quali altri oggetti hanno delle dipendenze, è il container che è il responsabile del collegamento fra questi oggetti, è il container che "inietta" le dipendenze tra gli oggetti, da cui il nome di Inversion of Control, in questo modo non è necessaria una classe che faccia dei lookup per trovare i servizi, ma sono i servizi che vengono resi disponibili in un container.
Esistono tre tipi di implementazione dell' Inversion of Control

  • tipo1 I servizi implementano una interfaccia dedicata (Avalon)
  • tipo2 (Setter based) Le dipendenze vengono assegnate settando dei valori a dei JavaBeans (Spring e HiveMind)
  • tipo3 (Constructor based) Le dipendenze vengono assegnate, passando degli argomenti ai costruttori (PicoContainer)

Andremo ora a vedere come questo pattern viene usato nel mondo reale in un framework.

 

Descrizione Spring
Spring è un framework J2ee "leggero", in opposizione ad approcci cosidetti "pesanti"di altre tecnologie J2ee.
Non costringe a sposare integralmente le funzionalità che offre, essendo costruito in maniera modulare (i moduli sono contenuti in jar separati), questo consente di continuare ad utilizzare tool o altri framework (es. Struts).
Fornisce delle soluzioni molto eleganti e molto semplici dal punto di vista del scrittura del codice.
Andiamo a vedere il moduli di cui è composto:


Figura 1

 

  • Il package Core contiene le parti fondamentali per realizzare l' IoC. L' IoC viene realizzato per mezzo della classe BeanFactory che usando il pattern factory rimuove la necessità di singleton e permette di disaccopiare la configurazione e le dipendenze.
  • Il package Context fornisce l' accesso ai bean, fornisce il supporto per i resources-bundle, la propagazione degli eventi, il caricamento delle risorse e la creazione trasparente dei contesti.
  • Il package DAO (data access object) fornisce uno strato di astrazione per JDBC.
    Fornisce una modalità dichiarativa per la gestione delle transazioni (transaction management) non solo per alcune classi, ma per tutti i POJO che siano eventualmente necessari.
  • Il package ORM fornisce l' integrazione con i più diffusi object-relational mapping (JDO, Hibernate e iBatis), sfruttando le caratteristiche di transazionalità definite precedentemente.
  • Il package AOP fornisce una implementazione aderente alle specifiche AOP(***) Alliance dell' Aspect programming.


Il package WEB fornisce l 'integrazione con le caratteristiche orientate al web, come inizializzazione di contesti usando Servlet listener, e la creazione di contesti applicativi per applicazioni web.
Questo è il package da usare nel caso si voglia integrare Struts oWebWork. Il package WEB MVC fornisce una implementazione Model View Controller per applicazioni web, fornendo inoltre tutte le altre caratteristiche di Spring.
L'uso di tutti o di una sola parte dei moduli di Spring consente l' uso in diversi scenari:

Figura 2


Come middle-tier di un framework web



Figura 3


In uno scenario remoto:



Figura 4


I protocolli Hessian e Burlap, sono protocolli per la connessione con i web-services,
Hessian è un protocollo binario (sopra la chiamata RPC), Burlap è invece un protocollo xml, usato in particolare con gli EJB.
Oppure con gli EJB per fare da wrapper per i POJO (Plain old Java Objects)



Figura 5

 

Classi che realizzano l' IoC
Andiamo a vedere quali sono le classi che realizzato l'IoC in Spring:

org.springframework.beans.BeanFactory

L' interfaccia BeanFactory è il "container" che istanzia, configura, e gestisce i bean.
Questi collaborano con altri e perciò hanno delle dipendenze con essi, queste dipendenze si riflettono nei dati di configurazione (o a runtime) usati da questa classe
Una sua implementazione è XmlBeanFactory, ecco cosa serve per usarla nel modo più semplice:

InputStream is = new FileInputStream("beans.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);


il file xml che usa la XmlBeanFactory contiene la definizione dei bean da gestire:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id= ".." class"..">
...
</bean>

<bean id= ".." class"..">
...
</bean>
</beans>


Come abbiamo detto precedentemente possimo avere vari approcci per l' IoC esaminiamo quelli di tipo 2 e tre (Spring li può usare entrambi)

setter-based: l' iniezione delle dipendenze è realizzata chiamando i metodi set sui bean , dopo averli istanziati con un costruttore senza argomenti o una factory.
Spring usa principalmente questo approccio:

constructor-based:l' iniezione delle dipendenze è realizzata invocando un costruttore con un numero di parametri, ciascuno di questi rappresenta un collaboratore o una proprietà.
In aggiunta, si può specificare o il metodo di creazione o il costruttore.
Spring supporta anche questo approccio, per supportare eventuali bean preesistenti che sono forniti di soli costruttori e non hanno metodi set.
Perciò a seconda dell' approccio scelto valorizzeremo le definizioni dei bean nel metodo opportuno.

 

Esempio pratico: Jetspeed2
Prendendo Jetspeed2 come esempio pratico vediamo il file jetspeed-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<!--
Copyright 2004 The Apache Software Foundation

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<beans>


<!-- Commons configuration object generated from jetspeed.properties -->
<bean id="portal_configuration"
class="org.apache.commons.configuration.PropertiesConfiguration">
<constructor-arg>
<value>${applicationRoot}/WEB-INF/conf/jetspeed.properties</value>
</constructor-arg>
</bean>

<!-- ServletConfig -->
<bean id="javax.servlet.ServletConfig"
class="org.apache.jetspeed.components.factorybeans.ServletConfigFactoryBean"
/>



<!-- Portlet Services -->
<bean id="PortalServices"
class="org.apache.jetspeed.services.JetspeedPortletServices" >
<constructor-arg>
<map>
<entry key="PortletRegistryComponent">
<ref bean="org.apache.jetspeed.components.portletregistry.PortletRegistryComponent" />
</entry>
<entry key="PAM">
<ref bean="PAM" />
</entry>
</map>
</constructor-arg>
</bean>

<!-- Template Locators -->
<bean id="TemplateLocator"
class="org.apache.jetspeed.locator.JetspeedTemplateLocator"
init-method="start" destroy-method="stop"
>
<constructor-arg>
<list>
<value>${applicationRoot}/WEB-INF/templates</value>
</list>
</constructor-arg>
<constructor-arg><value>${applicationRoot}</value></constructor-arg>
</bean>


<!-- Request Context -->
<bean id="org.apache.jetspeed.request.RequestContextComponent"
class="org.apache.jetspeed.request.JetspeedRequestContextComponent"
>
<constructor-arg ><ref bean="org.apache.jetspeed.container.session.NavigationalStateComponent" /></constructor-arg>
<constructor-arg ><value>org.apache.jetspeed.request.JetspeedRequestContext</value></constructor-arg>
<constructor-arg ><ref bean="org.apache.jetspeed.userinfo.UserInfoManager" /></constructor-arg>
</bean>

<!-- Portlet Window Component -->
<bean id="org.apache.jetspeed.container.window.PortletWindowAccessor"
class="org.apache.jetspeed.container.window.impl.PortletWindowAccessorImpl"
>
<constructor-arg ><ref bean="org.apache.jetspeed.components.portletentity.PortletEntityAccessComponent" /></constructor-arg>
</bean>

<!-- Pluto Portlet Container -->
<bean id="Pluto" class="org.apache.pluto.PortletContainerImpl" />

<!-- Jetspeed 2's wrapper around Pluto -->
<bean id="org.apache.pluto.PortletContainer"
class="org.apache.jetspeed.container.JetspeedPortletContainerWrapper" >
<constructor-arg ><ref bean="Pluto" /></constructor-arg>
</bean>
......
</beans>


In esso possiamo vedere le definizioni dei bean nel quale possono essere posti gli argomenti per i costruttori, si è scelto quindi un approccio di tipo 3 (Per la lista completa si veda la documentazione di Spring).
Andiamo a vedere come accediamo nel codice agli oggetti

org.apache.jetspeed.components.SpringComponentManager

In questo file sono descritti gli oggetti, e le dipendenze eventuali, che Spring inietterà:

/*
* Copyright 2000-2001,2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jetspeed.components;

import java.util.ArrayList;
import java.util.Collection;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;


public class SpringComponentManager implements ComponentManager
{
protected FileSystemXmlApplicationContext appContext;

protected ArrayList factories;

public SpringComponentManager(String[] springConfigs, ApplicationContext parentAppContext)
{
factories = new ArrayList();
appContext = new FileSystemXmlApplicationContext(springConfigs, parentAppContext );

factories.add(appContext);
}

public Object getComponent( Object componentName )
{
if(componentName instanceof Class)
{
return appContext.getBean(((Class)componentName).getName());
}
else
{
return appContext.getBean(componentName.toString());
}
}


...

public Object getRootContainer()
{
return appContext;
}


public Collection getContainers()
{
return factories;
}


public void stop()
{
appContext.close();

}
}

In questo codice vediamo come è possibile recuperare gli oggetti iniettati con le loro dipendenze ognuno nel proprio ApplicationContext (in questo caso uno per ogni file con le descrizioni dei bean).

 

Conclusione
Il Dependency Injection (o Inversion of Control) è una alternativa agile che permette di centralizzare i servizi in una applicazione, permette di integrare velocemente, in maniera elegante, semplice e in più modalità i propri servizi iniettandoli.
Questo approccio si contrappone per filosofia al pattern ServiceLocator che funge da localizzatore delle risorse mascherando le necessarie operazioni per il loro reperimento.

 

Semplicità e Agilità
La filosofia con cui è stato costruito Spring è la semplicità. Rod Johnson, uno degli ideatori di Spring ha illustrato nel libro "J2EE Development without EJB" i problemi derivanti dalla costruzione di applicazioni J2EE adottando un approccio che sposi tout-court le soluzioni ortodosse" in cui la complessità non porta nessun beneficio quando non sia l' unica strada. Documenta (e l' esperienza quotidiana lo conferma) come la semplicità e l' agilità sono la strada migliore per ottenere la migliore produttività, la semplicità del codice, la manutenibilità e il refactoring (e anche la soddisfazione dello sviluppator). Dimostra il perchè una applicazione J2ee non è per sua natura complessa, ma lo può essere se nella sua costruzione vengono usati strumenti che non sono i più indicati. Dopo aver letto "J2EE Development without EJB" in cui viene spiegata e documentata questa idea, le metodologie agili per raggiungerla (oltre a illustrare l 'uso Spring), vengono spontanee le parole: benvenuta semplicità.

 

Bibliografia e riferimenti
[1] Dependency Injection Martin Fowler: http://www.martinfowler.com/articles/injection.html
[2] ServiceLocator :http://java.sun.com/blueprints/patterns/ServiceLocator.html

[3] Spring :http://www.springframework.org/
[4] Jetspeed2 : http://portals.apache.org/jetspeed-2/
[5] Avalon : http://avalon.apache.org/
[6] PicoContainer : http://www.picocontainer.org/
[7] Hibernate : http://www.hibernate.org/
[8] J2EE development with out EJB: http://www.wiley.com/WileyCDA/WileyTitle/productCd-0764558315.html

Massimiliano Dessì (http://www.jugsardegna.org/vqwiki/jsp/Wiki?MassimilianoDessì) è raggiungibile a desmax74@yahoo.it oppure massimiliano.dessi@gruppoatlantis.com (oltre a mdessi@mokabyte.it). Ha iniziato a lavorare presso la Sistemi Informativi S.p.A (IBM come programmatore Java.
Dal 2001 anni lavora presso Atlantis S.p.A.dove utilizzando metodologie agili quali l'eXtreme programming (Xp), sviluppa applicazioni enterprise Web-based con tecnologia J2EE quali portali e content management system per la promozione del territorio. Nel tempo libero contribuisce al progetto open-source jakarta -Jetspeed, di cui ha realizzato il servizio di localizzazione e la portlet per la localizzazione nelle varie lingue, cura la versione italiana
di Jetspeed.
Laureando in Ingegneria Elettronica presso l'Università di Cagliari.


MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it