Introduzione
In questo post trattiamo l’integrazione di tre tecnologie che sono parte della specifica J2EE 6, ovvero:
- CDI Contexts and Dependency Injection (JSR-299);
- Dependency Injection For Java (JSR-330);
- JavaServer Faces 2.0 (JSR-314).
Iniziamo con fare un po’ di chiarezza tra le due specifiche JSR-299 e JSR-330. Quest’ultima fornisce una specifica per l’implementazione della Dependency Injection attraverso annotation. E’ molto basilare e definisce un certo numero di annotazioni standard contenute nel package javax.inject
, nel quale troviamo i seguenti elementi: Inject
, Qualifier
, Scope
, Singleton
, Named
e Provider.
Elementi che sono alla base della definizione del significato di dependency injection. La specifica JSR-299 invece, utilizza i concetti della JSR-330 estendendola con i concetti di modularità, decorator, interceptor, custom scopes, etc. Volendo fare un paragone, la relazione tra tali specifiche è simile a quella esistente tra JPA e JDBC. JPA utilizza JDBC internamente, ma l’utente è ancora in grado di utilizzare JDBC senza JPA. Non c’è quindi una sovrapposizione tra le due specifiche.
CDI (Contexts and Dependency Injection)
La specifica JSR-299 fornisce un’architettura che consente a componenti standard di J2EE, come servlet ed EJB, di esistere all’interno del ciclo di vita di una applicazione enterprise con uno scope ben preciso. Inoltre, CDI fornisce servizi che consentono a EJB session bean e JSF managed bean, di essere iniettati e di interagire in modo scarsamente accoppiato, attraverso l’utilizzo di un modello ad eventi. Più semplicemente CDI unifica e semplifica i modelli di programmazione EJB e JSF, consentendo agli enterprise bean di agire come managed bean in una applicazione JSF. Inoltre, attraverso i suoi servizi, CDI porta il supporto transazionale al livello web, rendendo molto più semplice l’accesso alle risorse transazionali in applicazioni di questo tipo. Ad esempio, con CDI è immediato costruire una applicazione web Java EE che accede a un database con la persistenza fornito dal Java Persistence API.
In realtà, il concetto di managed bean va quindi oltre la specifica CDI. I managed bean, introdotti in Java EE 6, sono stati progettato per unificare tutti i vari tipi di bean in Java EE, tra cui JSF managed bean, enterprise bean, e CDI bean. Un managed bean non è altro che una classe Java che viene trattato come un componente gestito dal container Java EE. Opzionalmente, è possibile dargli un nome nello stesso spazio dei nomi utilizzato dai componenti EJB. Un managed bean può quindi contare su un piccolo numero di servizi forniti dal container, per lo più legate alla gestione del ciclo di vita e l’iniezione di risorse. Altre tecnologie Java EE come il JSF, EJB, e CDI sono costruire su questa definizione di base di un managed bean con l’aggiunta di specifici servizi. Così, per esempio, un managed bean JSF aggiunge lo scope al ciclo di vita del componente, un session bean EJB aggiunge servizi come il supporto per le transazioni, e un CDI bean aggiunge servizi come l’iniezione di dipendenza.
Una Applicazione di Esempio
Torniamo al progetto jsf-employees descritto nel post Creare Web App con JSF 2.0 e completiamolo con i service necessari alla ricerca degli impiegati (similmente a quanto visto in Create Web App con Bootstrap basato però su servlet e non su JSF).
Innanzitutto aggiorniamo le dipendenze del progetto considerando che gli Application Server conformi alla specifica J2EE includono al loro interno una implementazione di CDI. Ad esempio WildFly, JBoss EAP, GlassFish, IBM WebSphere Application Server (dalla 8.5.5 in poi) ed Oracle WebLogic utilizzano JBoss Weld, l’implementazione di riferimento di CDI. Diversamente nei Servlet Container come Apache TomCat and Eclipse Jetty, CDI no è incluso e deve quindi essere fornito dal progetto.
Nel nostro esempio ho utilizzato WildFly 8, quindi inseriamo nel pom.xml
la dipendenza a Weld:
1 2 3 4 5 6 |
<dependency> <groupId>javax.enterprise</groupId> <artifactId>cdi-api</artifactId> <version>1.2</version> <scope>provided</scope> </dependency> |
si noti che lo scope della dipendenza è provided
come ci aspettavamo. Per completare la configurazione è obbligatoria la presenza nel progetto del file beans.xml
, anche completamente vuoto (ha contenuto solo in alcune situazioni limitate). Per una applicazione web, tale file deve essere collocato nella directory WEB-INF
, mentre per i moduli EJB o file JAR, il file deve essere collocato nella directory META-INF
.
1 2 3 4 5 6 |
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" version="1.1" bean-discovery-mode="annotated"> </beans> |
Si noti in particolare l’attributo bean-discovery-mode
utilizzato dallo scanner per implementare la strategia di riconoscimento dei CDI managed bean. In particolare il valore annotated
indica di identificazione è basata sull’uso delle annotazioni (@Named
in particolare).
Completata la configurazione creiamo il servizio di recupero dei dipendenti per la nostra applicazione implementandolo come managed bean. Non utilizziamo una base dati quindi il servizio utilizza semplicemente una lista popolata a priori con un insieme di nominativi.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@Dependent public class EmployeeService { List<Employee> employeeList = EmployeeList.getInstance(); // Get all employees. public List<Employee> getAllEmployees() { return employeeList; } // Get all employees whose Name or Last Name match the searchParam, order by // name and last name. public List<Employee> searchEmployeesByName(String searchParam) { ... } // Get the employee by id public Employee getEmployee(long id) throws Exception { ... } } |
Ciò che rende tale classe un CDI managed bean è l’annotazione @Dependant
. Durante la fase di inizializzazione dell’applicazione CDI esegue uno scan alla ricerca di tutti i bean annotati con una bean defining annotation. Al nostro servizio è quindi associato un nome di default che sarà employeeService
ma che può essere modificato utilizzando l’annotazione @Named
.
Proseguiamo definendo il JSF managed bean che sarà responsabile dell’interazione con la maschera di ricerca.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
@ManagedBean @SessionScoped public class EmployeeManagedBean implements Serializable { private static final long serialVersionUID = -2977688161569868585L; // Bean iniettato da CDI @Inject EmployeeService employeeService; // Nome del dipendente da ricercare private String employeeName; // Risultato della ricerca private List<Employee> result = null; public String getEmployeeName() { return employeeName; } public void setEmployeeName(String employeeName) { this.employeeName = employeeName; } public List<Employee> getResult() { return result; } public void setResult(List<Employee> result) { this.result = result; } public String search() { if ( employeeName == null || employeeName.isEmpty() ) { result = employeeService.getAllEmployees(); } else { result = employeeService.searchEmployeesByName(employeeName); } return null; } } |
L’iniezione del bean EmployeeService
avviene annotando la proprietà employeeName
con @Inject
. Si noti che non vi è necessità di creare i getter ed i setter per tali proprietà. Il metodo search
rappresenta l’action invocata dall’interfaccia per eseguire la ricerca. Esso restituisce null
indicando a JSF di ricaricare la stessa pagina da cui è stato invocato.
Infine vediamo la maschera di ricerca contenuta nel file index.hxtml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"> <h:head> <title>Employee Service</title> </h:head> <h:body bgcolor="white"> <h2>JSF 2.0 Employee Service Example</h2> <h:form id="employeeSerachForm"> <h:panelGrid> <h:outputText value="Ricerca Impiegati"/> <h:outputText value="Powered By http://www.javaboss.it"/> </h:panelGrid> <h:panelGrid columns="3"> <h:outputText value="Nome"/> <h:inputText value="#{employeeManagedBean.employeeName}"/> <h:commandButton value="Search" action="#{employeeManagedBean.search}"/> </h:panelGrid> <h:dataTable value="#{employeeManagedBean.result}" var="emp" rendered="#{not empty employeeManagedBean.result}"> <h:column> <!-- column header --> <f:facet name="header">ID</f:facet> <!-- row record --> #{emp.id} </h:column> <h:column> <f:facet name="header">Name</f:facet> #{emp.name} </h:column> <h:column> <f:facet name="header">Surname</f:facet> #{emp.lastName} </h:column> <h:column> <f:facet name="header">Department</f:facet> #{emp.department} </h:column> <h:column> <f:facet name="header">Email</f:facet> #{emp.email} </h:column> </h:dataTable> </h:form> </h:body> </html> |
L’interfaccia utilizza tutti componenti standard di JSF ed in particolare il dataTable che itera su una lista per creare una tabella HTML. La connessione tra interfaccia e bean avviene attraverso espressioni EL (Expression Language) le quali referenziano il managed bean recuperandolo dallo specifico contesto.
Se tutto è andato nel modo giusto avviando WildFly e aprendo la pagina http://localhost:8080/jsf-employees/ dovrebbe comparire:
Download Codice Sorgente
Il progetto completo è disponibile qui jsf-employees.