Primi passi con JBoss 7: nuovi progetti alle prese con il ClassLoader

JBoss 7 è uscito ormai da diverso tempo: vale la pena cominciare ad avvicinarsi e ammirare se le promesse degli sviluppatori sono state mantenute! Già da subito si rimane sbalorditi dalla velocità con cui si avvia: tutto tempo e soprattutto stress risparmiato! Tra le diverse novità, emerge anche la nuova gestione del ClassLoader che passa dalla classica versione gerarchica a quella per moduli. A fronte di maggior sicurezza e isolamento, siamo costretti a stare più attenti a come “confezioniamo” l’EAR delle nostre applicazioni: pena una bella serie di ClassNotFoundException! Ma andiamo per gradi e cominciamo dall’inizio.

JBoss Tools

Chi ha già sviluppato su JBoss sa che per lavorare insieme ad Eclipse sono necessari i JBoss Tools che forniscono un adapter per il server e una serie di strumenti di supporto allo sviluppo. E’ possibile scaricarli e installarli direttamente dall’Eclipse MarketPlace, attendere un po’ (non pochissimo a dire il vero) e al riavvio dell’IDE ci troviamo davanti una bella paginetta con una serie di shortcut alle funzionalità principali abilitate dai tools.

Il mio primo progetto

La modularità nello sviluppo è importante, infatti JavaEE porta a lavorare proprio in quella direzione: non a caso gli “Enterprise Archive (EAR)” sono composti da diversi moduli. Normalmente, in un EAR ci aspettiamo di trovare almeno un:

  • WAR (Web Archive): modulo che contiene l’interfaccia web;
  • EJB-JAR (Enterprise Java Bean): modulo che implementa la logica di dominio;
  • JPA-JAR (Java Persistence API): modulo che contiene le entità del dominio;

Vediamo come questa struttura si riflette in Eclipse. Cominciamo col creare un “Enterprise Application Project“: File -> New -> Enterprise Application Project:

Creazione progetto Enterprise: step 1

Se non abbiamo ancora creato un server clicchiamo su “New Runtime” e puntiamo alla cartella dove risiede JBoss. Dal wizard inoltre sarà possibile creare anche i moduli principali, ovvero un nuovo progetto Web e uno EJB.

Creazione progetto Enterprise: step 2

Al termine, ci troveremo nel workspace i seguenti progetti.

  • MyEnterpriseApp
  • MyEnterpriseAppWeb
  • MyEnterpriseAppEJB

E possibile a questo punto creare un progetto EJB Client che conterrà le sole interfacce degli EJB: click destro sul progetto EJB -> Java EE Tools -> Create EJB Client Jar. Verrà creato il progetto MyEnterpriseAppEJBClient.

Adesso non ci rimane che il modulo JPA. File -> New -> JPA Project: scegliamo di chiamarlo per esempio MyEnterpriseAppJPA e aggiungiamolo all’EAR MyEnterpriseApp (come in figura): seguiamo poi il wizard e scegliamo di creare un progetto JPA Generico.

JPA Wizard

Al termine, il workspace sarà arricchito di due progetti:

  • MyEnterpriseAppEJBClient
  • MyEnterpriseAppJPA

Cerchiamo ora di capire come legare i cinque progetti che abbiamo creato in modo da risolvere tutte le dipendenze sia in compilazione che in esecuzione.

Legare i progetti tra di loro: Compile Time vs Run Time

Questa è la fase che di solito crea più confusione. Certe volte non è chiaro come mai in fase di scrittura del codice le dipendenze vengano risolte bene, mentre a runtime ci imbattiamo con eccezioni del tipo ClassNotFoundException. Distinguiamo quindi dipendenze a

Compile Time
sono le dipendenze necessarie per compilare i progetti. Si impostano tramite la voce Build Path, che si trova tra le proprietà del progetto;
Runtime
sono le librerie che verranno incluse nell’EAR e che saranno necessarie all’Application Server per far girare la nostra applicazione. Da Eclipse Helios in poi, vengono gestite dalla voce Deployment Assembly delle proprietà del progetto, che sostituisce la vecchia Java EE Module Dependencies (effettivamente meno chiara).

Modulo EJB

Cominciamo quindi dal modulo EJB Client: Click destro sul progetto -> Build Path… -> Configure Build Path. Compariranno una serie di tab:

EJB Client build path
  • nel tab “Projects” aggiungiamo il progetto JPA (MyEnterpriseAppJPA);
  • spostiamoci sul tab “Order and Export” e selezioniamo il progetto appena aggiunto: da adesso in poi, ogni progetto che importerà EJB Client, farà anche automaticamente riferimento al progetto JPA in compile time.

Modulo Web

Passiamo adesso a considerare la parte web. Per accedere alla logica di dominio che implementeremo negli EJB, basta legare il modulo Web a quello EJB Client, in modo da separarlo dalle implementazioni: questa configurazione permette di rendere le implementazioni degli EJB, siano essi remoti o locali, trasparenti al modulo web. Il Build Path sarà:

Build Path WAR

Per quanto detto prima, il modulo Web risolverà automaticamente anche le dipendenze con il modulo JPA.

Archivio Enterprise

Non ci resta che vedere come organizzare i moduli all’interno dell’EAR: controlliamo il suo Deployment Assembly dalle proprietà del progetto:

EAR Deployment Assembly

Dovrebbe apparire così come in figura: tutti i moduli a livello di root, ad eccezione di quello JPA che viene archiviato nella cartella lib.

Primi preparativi

Ammettiamo che la nostra prima applicazione sia un gestionale di ordini: avremo bisogno per esempio dell’entità “ordine” da creare nel progetto JPA:

@Entity
@Table(name="orders")
public class Order implements Serializable {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private long id;
   
   private String description;

   //Getters and Setters
}

e di un EJB capace di salvare un ordine e di recuperarlo in base al suo id

@Stateless
public class OrderEJB implements OrderEJBLocal {

   @PersistenceContext(unitName = "MyEnterpriseAppJPA")
   private EntityManager entityManager;
   
   @Override
   public Order findById(long id) {
      return this.entityManager.find(Order.class, id);
   }
   
   @Override
   public void save(Order order) {
      this.entityManager.persist(order);
   }

}

molto molto semplici a scopo di test. Ricordiamo che l’interfaccia OrderEJBLocal sta nel progetto EJB Client. Se lasciate creare gli EJB al wizard di Eclipse, ci penserà lui a dividere le classi tra i progetti EJB in modo opportuno.

Per la parte web infine, basta una semplice pagina (JSF per esempio) di test con pochi campi necessari a creare l’ordine.

Ultimi preparativi: un persistence.xml tipo il seguente

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
   <persistence-unit name="MyEnterpriseAppJPA">
      <jta-data-source>jdbc/MySQL</jta-data-source>
      <properties>
         <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect"/>
         <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
         <property name="hibernate.show_sql" value="true"/>
         <property name="hibernate.hbm2ddl.auto" value="update"/>
      </properties>
   </persistence-unit>
</persistence>

e il JDBC driver (MySQL in questo esempio) da copiare nella cartella di deploy del server (eh si, proprio accanto al vostro EAR pubblicato…). Non dimentichiamoci infine del Data Source! E’ facilissimo crearlo dalla console di amministrazione del server (di default su localhost:8080 a server attivo): basta selezionare il driver (che comparirà tra quelli disponibili una volta copiato nella cartella di deploy), dare un nome JNDI (jdbc/MySQL in questo caso) e poche altre informazioni!

Si parte… o quasi: problemi di ClassLoader!

A questo punto siamo pronti per partire: avviamo il server, deployamo l’applicazione e… ecco la prima sorpresa!! L’entità Order non viene risolta dal modulo EJB Client durante l’avvio!!!!

ClassNotFoundException

Colpa del classloader… Come avevamo accennato all’inizio, JBoss 7 non implementa più un classloader gerarchico, ma diviso per moduli: l’eccezione è il chiaro sintomo che il modulo EJB Client e quello JPA sono stati caricati da due classloader differenti! Visto che fino a JBoss 6 questa configurazione funzionava correttamente come fare?! Dopo aver approfondito un po’ l’argomento, prima di cominciare a sviluppare è bene tenere presente alcuni concetti base:

  • All’interno dell’EAR, ogni modulo è caricato con un classloader differente.
  • All’interno dell’EAR, il/i moduli Web hanno accesso al/ai moduli EJB, i quali possono avere accesso tra di loro. I moduli EJB però non hanno accesso al modulo Web.
  • Tutto il contenuto del WAR (ovvero WEB-INF/classes e WEB-INF/lib) è visto come modulo unico, caricato quindi con lo stesso classloader.
  • Tutto il contenuto della cartella EAR/lib è caricata con lo stesso classloader.
  • L’EAR mette a disposizione dei suoi moduli l’accesso alla cartella lib.
  • Al momento del deploy, a seconda del tipo di modulo, viene dato accesso alle dipendenze del server (Automatic Dependencies).

Alla luce di tutto ciò, torniamo a considerare il Deployment Assembly dell’EAR visto in precedenza: per come sono impostati, è evidente che EJB Client e JPA hanno classloader diverso! Come mai però il modulo JPA è finito nella cartella lib?! Controlliamo le facets del progetto: click destro sul progetto JPA -> Properties -> Project Facets:

Project Facets JPA

Tra le varie cose, troviamo il check su “Utility Module“: il progetto sarà quindi referenziabile dai moduli all’interno dell’EAR. E’ per questo che viene automaticamente inserito nella cartella lib! Per risolvere il problema, allora basta marcare come “Utility Module” anche il progetto EJB Client in modo che venga messo nella cartella lib e venga caricato con lo stesso classloader del progetto JPA. Andiamo a controllare le sue facets e tristemente scopriamo che il check ce l’ha già 🙁

Project Facets EJB Client

Eppure è strano che non sia stato messo nella cartella lib… Non è che ci ha giocato un brutto scherzo Eclipse? Torniamo nel Deployment Assembly dell’EAR e facciamo la prima cosa che un informatico farebbe: rimuoviamo il progetto EJB Client e rimettiamolo: come per magia adesso verrà messo nella cartella lib!!!

EAR Deployment Assembly

Della serie quando il vecchio trucco spegni e riaccendi funziona sempre…

Pronti? Avviamo nuovamente il server e questa volta vedremo che le dipendenze verranno risolte correttamente!

Andrea Como

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+

  • Pingback: JBoss 7.0.2 e SOAP: come abilitare il modulo Web Services | Cose Non Javiste()

  • Alex

    Ottimo articolo! Mi trovo un piccolo problema con la tua implementazione. Ho annotato un campo dell’entità indicandolo come unique ( @Column(unique=true) ) ed ho simulato l’inserimento di valori duplicati. Giustamente ottengo l’eccezione come previsto. Come fare a gestire quell’eccezione???
     

    • Ciao! Non so se ho capito bene il problema. L’eccezione che dovrebbe lanciarti credo sia UniqueConstraintException (o qualcosa di simile). Potresti catchere la specifica eccezione e rilanciarla “wrappandola” in una tua eccezione checked (che puoi chiamare per esempio ApplicationException), in modo da poter essere gestita dal chiamante per riportare l’errore all’interfaccia utente in qualche modo (dipende poi da che framework usi per la vista…). Spero d’aver risposto alla domanda!

  • Alex

    Ottimo articolo! Mi trovo un piccolo problema con la tua implementazione. Ho annotato un campo dell’entità indicandolo come unique ( @Column(unique=true) ) ed ho simulato l’inserimento di valori duplicati. Giustamente ottengo l’eccezione come previsto. Come fare a gestire quell’eccezione???