Le specifiche di Java EE 6

III parte: Ancora su Enterprise Application Technologies e JSR-299di

Continuiamo la nostra serie di articoli dedicati allo standard Java EE in cui raccontiamo le tecnologie che questo standard racchiude in se‘, cosa si propone di innovare e le motivazioni che hanno spinto la Sun a iniziare a delinearlo già nel lontano 1998.

La parte più importante delle specifiche Java EE è senza dubbio quella che riguarda le tecnologie enterprise, prima fra tutte la tecnologia CDI. In questo articolo tratteremo di CDI mostrando i pattern implementati mediante questo standard.

Prima di inizare a trattare CDI più nel dettaglio, riprendiamo per un momento il discorso interrotto nell'articolo precedente, partendo proprio dai test che abbiamo effettuato per giocare con gli esempi esposti.

Produttività con CDI: CDI Advocacy e Arquillan

A conclusione dell'articolo passato abbiamo introdotto un framework per poter eseguire del codice basato su CDI senza utilizzare un application server completo. In effetti lo standard CDI è stato pensato proprio per poter dipendere solamente da SE e non da EE: questa scelta è stata fatta per garantire una produttività più alta rispetto allo standard degli EJB (scelta surrogata anche dall'esperienza negativa con gli EJB).

Purtroppo però non esiste un'interfaccia standardizzata di CDI per poterlo usare al di fuori di Java EE, e quindi ogni produttore di CDI ha utilizzato una propria interfaccia proprietaria per poter agganciare il framework all'interno di un contenitore Java EE. È per questo che nell'articolo precedente abbiamo utilizzato CDI Advocacy [1]: è un adattatore per CDI che uniforma le API di accesso dall'esterno di un contenitore Java EE a una delle implementazioni di CDI supportate, che sono Weld di JBoss e CanDI di Resin.

In questo articolo introdurremo invece un altro ottimo strumento per la produttività nel mondo Java EE che è Arquillan [2]. Arquillan è un prodotto più completo rispetto a CDI Advocacy: è un eccellente prodotto per il testing di applicazioni di tutto lo stack Java EE.

Utilizzeremo Arquillan per eseguire codice CDI ed EJB all'interno in un test case e senza la necessità di dover distribuire tutta una intera applicazione su un application server. Arquillan permette di eseguire dei test unitari creando degli archivi Java EE ad hoc ed eseguendo il codice di test all'interno di un contenitore Java EE embedded o remoto. In particolare è possibile eseguire un Unit Test (con JUnit o TestNG) nelle seguenti modalità:

  • Weld embedded. Arquillan crea una istanza della Reference Implementation di CDI (Weld) ed esegue il test (i binari di Weld vengono scaricati via Maven).
  • JBoss managed. Da una distribuzione di JBoss 7 già presente su disco, Arquillan fa partire una nuova istanza di JBoss ed esegue il test (è necessario quindi dire ad Arquillan dove è installato JBoss tramite la variabile di ambiente JBOSS_HOME);
  • Glassfish embedded. Arquillan crea una istanza di Glassfish 3 ed esegue il test (i binari di Glassfish 3 vengono scaricati via Maven).
  • JBoss remote. da un server JBoss già attivo, Arquillan installa ed esegue il test unitario; al termine del test deinstalla l'archivio del test.
  • Glassfish remote. Da un server Glassfish 3 già attivo, Arquillan installa ed esegue il test unitario; al termine del test deinstalla l'archivio del test.

Negli allegati di questo articolo ho messo un progetto Maven che permette di compilare del codice che fa uso dello standard Java EE 6 e di eseguire gli unit test negli scenari JBoss managed e JBoss remote.

Nota sulle implementazioni di CDI

Per inciso i maggiori produttori di CDI, ad oggi, sono:

  • JBoss Weld [3];
  • Apache OpenWebBeans [4];
  • Caucho Resin CanDI [5].

La Reference Implementation di CDI è Weld, di JBoss. Weld è usato anche all'interno di GlassFish v3 (oltre che all'interno di JBoss v7).

Architettura dello standard

Come da specifica (CDI expert group specs [7]), ecco i servizi che offre CDI ai componenti Java EE.

Ciclo di vita

Un primo servizio è la gestione del ciclo di vita di un bean e le interazioni dei componenti stateful legati a contesti ben definiti. Questo vuol dire che è il contenitore a gestire il ciclo di vita di un bean, e infatti un bean gestito da CDI non deve essere distrutto esplicitamente oppure condiviso esplicitamente da diversi componenti poiche' è il contenitore che si occupa di farlo in base al contesto specificato. Questo può sembrare "banale" ma in realtà non è cosi: queste due caratteristiche non sono infatti proprie degli EJB. Infatti, può essere necessario, a volte, dover esplicitamente rimuovere uno @Stateful EJB tramite @Remove quando scade la propria sessione; oppure può essere necessario passare un riferimento a una istanza di uno @Stateful EJB tra componenti diversi che condividono lo stesso contesto di sessione.

Tramite CDI oramai questo meccanismo non è più necessario, poiche' sarà cura di CDI di iniettare ad ogni componente Java EE l'istanza giusta in base al contesto definito.     Per esempio, se abbiamo due componenti Java EE diversi che hanno un riferimento ad un terzo componente Java EE (tramite @Inject), CDI inietterà loro la stessa istanza del managed bean a cui fanno riferimento, nel caso che i due componenti vivano nello stesso contesto. Se, invece, i due componenti vivono in due contesti diversi, allora CDI darà loro due istanze diverse. Come si comprende facilmente, si tratta di un meccanismo potente e molto comodo.

Dependency Injection

Altro aspetto importante è un sofisticato strumento type-safe di dependency injection, in particolare con la possibilità di poter scegliere quale tipo di implementazione di un bean iniettare di una stessa interfaccia.

Questa parte della specifica ha suscitato dei problemi all'interno dell'Expert Group delle nuove specifiche Java EE6. Infatti notiamo subito che questo requisito è esattamente lo scopo della JSR-330: evidentemente ci troviamo di fronte a una possibile confusione visto che abbiamo due standard diversi che dovrebbero implementare uno stesso pattern. Questo problema è poi stato risolto tramite l'invito da parte di Sun a "coordinare gli sforzi" [6]:

 

"In this respect, we request that this JSR-330 and JSR-299 coordinate their efforts so that they can jointly deliver a single, consistent and comprehensive dependency injection standard for the SE and EE platforms. Such coordination must take place before the Public Review of this JSR".

 

Ecco ora spiegato come mai abbiamo trattato prima di DI e poi di CDI anche se logicamente possiamo pensarli come un unico framework: il motivo è che CDI utilizza il meccanismo di dependency injection di un altro standard senza specificarne uno proprio, benche' il proprio scopo sia proprio quello di definire assieme un meccanismo di dependency injection e di contesto.

Expression Language

Va ricordata anche l'integrazione con Unified Expression Language (EL) in modo tale da poter essere utilizzato dentro le JSF.

Altre caratteristiche

Implementazione del pattern Decorator.

Implementazione del pattern Observer-Observable (event-notification model).

Definizione di un contesto web conversazionale in aggiunta ai contesti web già definiti (request, session e application context).

Lo standard deve poter essere estendibile: cioè deve essere possibile estenderne le funzionalità da parte di terze parti.

Relazioni con altri componenti della piattaforma EE

Ogni classe definita in un ambiente Java EE può iniettare dei bean tramite lo standard CDI. Ma in realtà il meccanismo di injection è già disponibile sin da Java EE 5: qual è quindi la differenza di questo nuovo meccanismo con quello pre-esistente? E come usarli assieme? Esaminiamo quindi le relazioni che ha CDI con il mondo EE preesistente.

Risorse

Lo standard Java EE 5 definisce delle interfacce per iniettare delle risorse esistenti in un ambiente EE (p.e.: un data source, destinatario di messaggi JMS, o una proprietà definita nell'ambiente del container, un JPA entity manager); questo meccanismo adesso dovrebbe essere sostituito dal nuovo meccanismo di @Inject che permette anche di garantire la coerenza del tipo iniettato. Infatti nello standard Java EE 5 si parlava correttamente di resource injection, non di dependency injection in senso di generale.

Oggi, con il nuovo standard, per coerenza, non si dovrebbero più utilizzare le annotazioni: @Resource, @PersistenceContext o @PersistenceUnit all'interno della nostra struttura di dipendenze, ma creare una classe di Risorse che contenga al suo interno le risorse EE iniettate tramite @Resource, @PersistenceContext o @PersistenceUnit ed esportarle all'esterno tramite @Inject. Ecco un esempio:

public class Resources {
       @SuppressWarnings("unused")
       @Produces
       @PersistenceContext
       private EntityManager em;
      
       @Produces
       @Resource
       private SessionContext context;
      
       @Produces
       public Logger produceLog(InjectionPoint injectionPoint) {
             return Logger.getLogger(injectionPoint.getMember()
                           .getDeclaringClass()
                           .getName());
       }
}

Questa classe è un esempio di implementazione di questa "direttiva" dello standard. Come si può vedere abbiamo "nascosto" le annotazioni di risorse di un entity manager, del contesto di sessione e di un logger. Ecco come usarle:

@Named
public class MemberRegistration {
       @Inject
       private Logger log;
 
       @Inject
       private SessionContext context;
 
       @Inject
       private EntityManager em;
...
       @Inject
       private ABean aBean;
 
       @Inject
       private AnotherBean anotherBean;
...
}

In questa maniera abbiamo coerentemente iniettato le risorse all'interno di MemberRegistration così come per tutti gli altri managed bean all'interno del contenitore.

EJB

Gli EJB oggi devono essere usati solo in riguardo dei seguenti concetti:

  • sicurezza basata sui ruoli (@Role);
  • transazioni (@Transaction);
  • thread-safe e scalabilità specificata via annotazioni @Stateful e @Stateless;
  • messaggi (@MessageDriven).

Come già detto, gli EJB sono stateful (cioè legati a una sessione web) oppure non hanno contesto web; inoltre devono essere esplicitamente distrutti (nel caso stateful), e può essere anche necessario passare la stessa istanza a client diversi se fanno riferimento alla stessa sessione.

La specifica CDI "migliora" l'uso degli EJB perche' permette di aggiungere una natura "contestuale" che prima essi non avevano. Infatti ogni istanza di un session bean ottenuto mediante dependency injection (@Inject) è una istanza contestuale. In tal senso, un EJB che si arricchisce quindi del concetto di web-context:

  • può essere usato all'interno di diversi client che condividono lo stesso contesto;
  • ha un ciclo di vita legato al ciclo di vita del contesto stesso che non necessariamente è quello della sessione, ma può essere quello della request oppure definito ad hoc dal programmatore.

Questo discorso vale solo per i session bean, mentre per i message driven bean G può esistere il concetto di G (sebbene anche i message driven bean vengano creati dal contenitore via CDI, non può essere applicato loro alcun concetto di contesto).

JSF

Il legame che c'è tra CDI e JSF risiede nella possibilità di utilizzare EL. In questo modo è possibile accedere ai campi e alle funzioni di un managed bean da pagina JSF tramite Expression Language.

Concetti fondamentali

Definiamo ora in modo formale [7] cos'è un bean per il contenitore CDI. Un Bean è un componente EE che è sorgente di oggetti contestuali (contextual instances of bean). Un Bean è composto da:

  • un insieme non vuoto di tipi;
  • un insieme non vuoto di qualificatori;
  • un context scope;
  • un nome EL;
  • un insieme di legami con degli interceptors;
  • una implementazione.

Un Bean è sostanzialmente una classe Java non astratta che viene gestita dal contenitore. Il contenitore ha il compito di creare e distruggere questi oggetti (e le loro dipendenze ricorsivamente) in base al contesto definito; le istanze contestuali possono essere iniettate in altre istanze contestuali via injection. Vediamo ora nel dettaglio le definizioni date sopra.

Bean Type

L'insieme dei tipi (bean type) è formato da classe del bean, l'insieme di classi che il bean estende e l'insieme delle interfacce che implementa. Il bean type è quindi un insieme di tipi che un client può usare per fare riferimento all'istanza creata.

Comunque, da linguaggio Java, non sempre si può fare il cast a uno qualsiasi dei tipi del bean: quasi sempre occorrerebbe utilizzare le interfacce per non incorrere in eccezioni di class cast exception a runtime.

Qualificatori

Ogni bean è automaticamente qualificato con @Default e/o con qualificatori definiti dallo sviluppatore. Un qualificatore rappresenta una semantica visibile dal client che è soddisfatta da una particolare implementazione di uno dei tipi del bean type Per esempio possiamo definire i qualificatori @Synchronous e @Asynchronous per poter dare la possibilità ai client di scegliere una implementazione sincrona oppure asincrona di un bean, e l'implementazione giusta verrà scelta automaticamente dal container all'interno di quelle disponibili nell'insieme del bean type. In questo caso, ovviamente, il client dovrà fare riferimento all'interfaccia del bean, appunto per evitare class cast exceptions.

Ogni Bean creato dal contenitore è associato a un oggetto che implementa l'interfaccia dello Standard javax.enterprise.inject.spi.Bean che contiene tutto ciò che serve per gestire le istanze di un certo bean:

public interface Bean extends Contextual {
       public Set getTypes();
       public Set getQualifiers();
       public Class getScope();
       public String getName();
       public Set> getStereotypes();
       public Class getBeanClass();
       public boolean isAlternative();
       public boolean isNullable();
       public Set getInjectionPoints();
}

Tramite questo template, il contenitore riesce a gestire (per ogni istanza che creiamo) il suo nome, l'insieme dei tipi, i qualificatori, etc.

Come si nota, Bean estende Contextual: ogni Bean creato dal contenitore, per definizione, deve rispettare la contextual interface (public interface Contextual) che definisce le callback create(...) e destroy(...).

L'interfaccia Contextual definisce le funzioni che servono per creare e distruggere l'oggetto associato in base alla validità del contesto (scope) definito per esso, quindi lo scope di validità viene chiamato contextual scope.

Scopes

Gli scopes servono per definire il ciclo di vita di un bean: mentre nella specifica DI un Bean veniva creato e "dimenticato", adesso invece è necessario un meccanismo che permetta di gestire il ciclo di vita. Adesso è necessario sapere quando un oggetto può essere creato e distrutto, e quindi fino a quando può rimanere in vita ed esserecondiviso da tutti i client. Gli scopes sono quindi stati introdotti da CDI, mentre gli altri componenti EE non hanno questa caratteristica, cioè non sono contestuali.

I componenti EE non Bean, quali possono essere EJB, servlet e JavaBean non hanno scopes ben definiti; questi componenti possono essere:

  • Singleton: come i nuovi Singleton EJB;
  • Stateless: come le servlet o gli Stateless Session Bean (SLSB);
  • Object: come i JavaBean e gli Stateful Session Bean (SFSB) che vengono creati e distrutti "a mano" dall'utente.

Come gli scope contestuali invece vengono automaticamente creati e distrutti dal container e il loro stato viene automaticamente condiviso da client che vivono nello stesso contesto.

I componenti EE non contestuali possono tuttavia essere "promossi" a bean contestuali se iniettati con @inject in Beans, oppure annotati con @Named (in questo modo vengono gestiti dal contenitore CDI).

Le annotazioni introdotte per gli scope contestuali sono @RequestScoped, @SessionScoped, @ConversationScoped, @ApplicationScoped. In ultimo abbiamo la definizione dello pseudo-scope @Dependent che è lo scope di default: indica che un oggetto viene creato ogni qualvolta se ne fa richiesta e non viene gestito dal contenitore.

Un esempio

Facciamo adesso un esempio di produzione di un Bean mediante scope: da specifica DI un metodo (o un campo) produce un altro Bean mediante l'annotazione @Produces. Adesso possiamo "estendere" il concetto base di DI aggiungendo il concetto di contesto da associare all'oggetto creato (cioè di "validità" dell'istanza creata):

       @Produces
       @SessionScoped
       @WishList
       public List getWishList() {
             ....
       }
 
       @Produces
       @ApplicationScoped
       @All
       public List getAllProducts() {
             ....
       }

Nell'esempio precedente abbiamo creato due produttori. Il primo è stato qualificato con @WishList, e produce una lista di prodotti che vive all'interno di una sessione web: sarà il contenitore a doverlo distruggere quando la sessione non sarà più valida; inoltre client diversi che condividono la stessa sessione condivideranno lo stesso Bean creato (lista di prodotti). Il secondo è stato qualificato con @All (è il qualificatore di default) che produce una lista di prodotti che vive all'interno di tutta la vita del contenitore: questa lista verrà creata la prima volta che viene richiesta e distrutta alla chiusura del contenitore.

Lato client si scriverà:

       ...
       @Inject @WishList
       private List wishListProducts;
       ...
       @Inject
       private List allProducts;
       ...

Come si vede, lato client non ci si pone il problema dello scope: è tutto a carico di chi produce le due liste. Analogamente è possibile definire lo scope di un Bean già all'atto della sua definizione di classe:

@Named
@RequesScope
public class MemberRegistration {
      
       @Inject
       private Logger log;
      
       @Inject
       private EntityManager em;
...
}

In questo modo il life-cycle di MemberRegistration verrà legato alla request. Da notare che in MemberRegistration abbiamo due campi iniettati da CDI. Con quale scope vengono gestiti?

Se non è altrimenti definito da un produttore, allora lo scope di Logger log e di EntityManager em sarà quello della classe stessa; altrimenti se, per esempio, l'entity manager ha uno scope di Applicazione, allora quando CDI provvederà a distruggere una istanza di MemberRegistration CDI non distruggerà l'istanza dell'entity manager dato che ha una validità di applicazione.

Conversation Scope

Una nota particolare merita lo scope @ConversationScope: mentre gli altri sono semplici da usare, questo nuovo tipo di scope è invece un po' più complesso da usare e merita di essere trattato più a fondo.

Lo scope "di conversazione" viene utilizzato in un sito web quando occorre memorizzare dei dati solo in alcune pagine, cioè all'interno di un workflow, e inoltre esiste una pagina in cui si inizia la "conversazione" e una o più pagine in cui la si termina. Un classico esempio è un wizard: in un wizard tutti i dati memorizzati andranno tenuti in vita solo all'interno del wizard stesso mentre alla fine si potrà distruggerli.

Un bean con contesto conversazionale deve avere al suo interno iniettato il bean Conversation, e utilizzare i metodi di start() ed end() per indicare quando la conversazione inizia e quando finisce. Per esempio:

@Named
@ConversationScope
public class Wizard {
 
       @Inject
       private Conversation conversation;
 
       public begin() {
              conversation.begin();
              conversation.setTimeOut(...);
       }
       public end() {
              conversation.end();
       }
...
}

E nella corrispettiva pagina JSF di inizio wizard (quella di fine è del tutto analoga):

...



...

Come si nota nell'esempio, è possibile settare anche il time out della conversazione in modo tale da farla durare per un tempo prestabilito.

Una nota sul ciclo di vita degli EJB

Cosa succede se utilizziamo un EJB come se fosse un bean contestuale? E che succede in caso di passivazione/attivazione? Come ho già detto, gli unici EJB a poter essere "promossi" ad avere un contesto sono gli Stateless Session Bean (SLSB) e gli Stateful Session Bean (SFSB).

Stateful Session Bean

Per quanto riguarda gli SFSB, quando viene creato un Bean (che è anche SFSB), il container crea il nuovo SFSB e lo ritorna al cliente. Poi, quando il Bean deve essere distrutto, se non era già stato distrutto viene chiamato @PreDestroy e poi viene distrutto, altrimenti viene ignorato.

Stateless Session Bean

Per quanto riguarda gli SLSB o anche degli EJB Singleton, il discorso è il seguente. Quando viene creato un Bean (che è anche SLSB), il container crea il nuovo SLSB e lo ritorna al cliente. Poi, quando il Bean deve essere distrutto, allora il container scarta l'istanza.

Passivazione/attivazione

Per quanto riguarda la passivazione e attivazione, c'è da dire che anche i Bean possono essere attivati/passivati: ma non tutti i Bean sono passivation capable. Per rendere un bean passivation capable basta renderlo serializzabile e far sì che tutti gli interceptor e decoratori definiti su esso lo siano. Per definizione gli EJB stateful sono passivation capable, mentre i singleton e gli stateless session beans non lo sono.

Pattern

Cominciamo adesso tutto il discorso riguardante i pattern implementati direttamente da CDI [8]. Sono pattern già noti e molto diffusi nel mondo Java EE, e CDI offre la possibilità di utilizzarli in maniera semplice attraverso l'uso di annotazioni.

Ecco di seguito l'elenco dei pattern che abbiamo visto fin qua e quelli che vedremo di seguito:

Patterns Creazionali

Factory Method (@Produces);

Singleton Factory Method (@Singleton).

Patterns Strutturali

Decorator (@Decorator).

Patterns Comportamentali

Alternatives (@Alternative);

Dependency Injection (@Inject);

Interceptors (@Interceptor);

Observer (@Observers, @Event).

Conclusioni

Per questo mese ci fermiamo qui, ma riprenderemo il discorso nel prossimo numero, ripartendo proprio dai pattern appena introdotti.

Abbiamo mostrato come CDI rappresenti una standardizzazione moderna dei vari framework IoC presenti sul mercato java, da Spring a Google Guice.

Principalmente CDI, originariamente chiamato web-beans, è uno standard orientato a semplificare l'uso delle JSF all'interno di un contenitore Java EE. Se questo standard riuscirà ad imporsi sul mercato o meno, è ancora presto per dirlo: manca ancora tutta una produzione di plugin che possano facilitarne l'integrazione con tecnologie web di terze parti, quali per esempio, no-sql DB (MongoDB, neo4j, Redis...), Android, oppure tutta una serie di estensioni che possano integrare facilmente il proprio sito web con i vari servizi AJAX di LinkedIn, Facebook o Google+. Tutti questi strumenti sono già presenti per esempio con Spring, e questo è indubbiamente un punto a sfavore di CDI.

Un punto invece a favore di CDI è la sua naturale modularità: ogni servizio web basato su CDI è per forza di cose modulare, quindi facilmente testabile ed estendibile. Arquillan, inoltre, rende i moduli CDI testabili semplicemente già sul server di riferimento di test, permettendo cosi di evitare di crearsi da soli un proprio ambiente di test.

Le feature di creazione implementate da CDI sono tutte presenti anche su Spring, ma non è sempre vero il contrario: per esempio su CDI non c'è modo di definire un ordine di creazione e distruzione dei bean (dependsOn), ne' tanto meno la possibilità di inizializzare un bean in modalità "lazy". Inoltre non abbiamo notato alcun meccanismo standard di injecting per le proprietà da file di properties (su Spring invece si può usare l'annotazione @Value tramite "Spring Expression Language").

Nonostante queste limitazione, CDI è un ottimo standard IoC modulare che permette di migliorare notevolmente il modello di programmazione degli EJB ereditato dal passato, inoltre anche se un framework affermato come Spring risulta ad oggi ancora superiore, sicuramente CDI è ampiamente migliorabile ed in futuro non mancheranno estensioni e plugin di terze parti (ad oggi esiste solo Seam 3 [9] di JBoss, che è un insieme di estensioni di Weld).

Ma, come detto, avremo modo di riparlarne nel prossimo numero.

Riferimenti

[1] CDI Advocacy

https://sites.google.com/site/cdipojo/

 

[2] Arquillan

www.jboss.org/arquillian

[3] JBoss Weld: sito ufficiale di weld

http://seamframework.org/Weld

[4] Apache Open Web Beans

http://openwebbeans.apache.org/owb/index.html

Apache TomEE: un interessantissimo web server che integra assieme

tomcat, spring, Weld, EJB, Hibernate

http://openejb.apache.org/apache-tomee.html

In questo link trovate un buon numero di esempi di come usare TomEE

sia per quanto riguarda CDI che tutte le altre tecnologie che integra

http://openejb.apache.org/examples-trunk/index.html

[5] Caucho Resin CanDI home page

http://www.caucho.com/resin-application-server/candi-java-dependency-injection/

[6] Articolo su InfoQ riguardo la confusione tra DI e CDI

http://www.infoq.com/news/2009/08/dependency-injection-javaee6

[7] CDI Expert Group reference documentation

http://jcp.org/en/jsr/detail?id=299

[8] Ecco un elenco di risorse su internet riguardanti i pattern nel mondo j2ee

Wikipedia sui patterns

http://it.wikipedia.org/wiki/Design_pattern

Erich Gamma: "Design Patterns: Elements of Reusable Object-Oriented Software"

http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

Core Java EE Design Patterns.

http://corej2eepatterns.com/Patterns2ndEd/

Observer pattern

http://it.wikipedia.org/wiki/Observer_pattern

Decorator pattern

http://en.wikipedia.org/wiki/Decorator_pattern

Interceptor pattern

http://en.wikipedia.org/wiki/Interceptor_pattern

[9] Sito ufficiale di Seam 3

http://seamframework.org/Seam3

 

 

Condividi

Pubblicato nel numero
175 luglio 2012
Michele Mazzei si è laureato in Scienze dell’Informazione nell’ormai lontano 1998. Si occupa di progettazione e scrittura di software in Java/Java EE e in C/C++ sul mondo Linux. Lavora a Roma in ambito spaziale maturando esperienze in ambito OGC, GIS, Map Server, Payload Data Ground Segment (PDGS). Si interessa di…
Articoli nella stessa serie
Ti potrebbe interessare anche