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.
|