Injection o lookup programmatico? Aiutati che CDI t’aiuta

Siamo tutti un po’ innamorati della Dependancy Injection (DI), come quella di CDI per esempio: ci piace per l’estrema eleganza del codice, per il disaccoppiamento tra classi, per mille altre ragioni del mondo, e un po’ anche per pigrizia! Purtroppo però, esistono situazioni in cui l’idillio si infrange e la DI non viene risolta dal nostro Inversion of Control (IoC) container preferito: ci sono infatti alcuni artefatti come i Converter e i Validator di JSF che non sono gestiti da questi contesti, non meno lo è CDI. Urge un lookup programmatico! Vediamo come fare.

“Through the looking glass”

Quando ci troviamo in quei contesti in cui l’injection non è disponibile, non dobbiamo rinunciare ai nostri bean gestiti e arricchiti dall’IoC container, perché significherebbe rinunciare a tutti i servizi che in essi vengono iniettati. All’interno di un Application Server JavaEE 6 compliant è possibile infatti fare programmaticamente ciò che il meccanismo di injection fa dietro le quinte.

Prendiamo per esempio JBoss 7 e Weld, l’implementazione di default di CDI: vediamo come possiamo raggiungere il BeanManager, cioè quella classe che gestisce il contesto CDI

CDI + JNDI

Sembra uno scioglilingua, ma è proprio così: è possibile recuperare il BeanManager direttamente dall’albero JNDI! Con che nome? Dando un occhio alla console di amministrazione del server, si scopre che risponde al nome di

java:comp/BeanManager

Possiamo quindi crearci una classe di utilità del tipo:

public class BeanManagerHelper {

  public static <T> T getBean(Class<T> clazz) {
    BeanManager bm = getBeanManager();
    Bean<T> bean = (Bean<T>) bm.getBeans(clazz).iterator().next();
    CreationalContext<T> ctx = bm.createCreationalContext(bean);
    return (T) bm.getReference(bean, clazz, ctx);
  }
	
  private static BeanManager getBeanManager() {
    try {
        InitialContext initialContext = new InitialContext();
        return (BeanManager) initialContext.lookup("java:comp/BeanManager");
    }
    catch (NamingException e) {
      throw new RuntimeException(e);
    }
  }
}

che ha lo scopo di restituirci un’istanza di una classe (passata al metodo getBean(...)) del contesto CDI in qualsiasi punto del codice! Recuperato infatti il BeanManager dal JNDI, con tre righe di codice possiamo ricongiungerci al nostro bean che credevamo irraggiungibile. Il codice del metodo getBean(...) è un po’ semplificato, perché non tiene conto di istanze multiple di una interfaccia, che avremo opportunamente distinto con un qualifier: fortunatamente il metodo bm.getBeans(...) accetta proprio questi qualifier come varargs per cui le modifiche da fare sono poche…

CDI + JSP/JSF

Se siamo sicuri di non trovare il BeanManager nell’albero JNDI, probabilmente è perché stiamo lavorando in Tomcat o Jetty, che non forniscono nativamente il supporto alla DI. Niente paura! Se siamo in uno di questi casi sicuramente abbiamo deployato Weld come Web Application library sotto WEB-INF/lib e registrato il listener di Weld nel web.xml:

<listener>
   <listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
</listener>

come suggerisce l’esempio “Number Guess” della documentazione del framework. Il listener provvederà a registrare nel ServletContext l’attributo:

javax.enterprise.inject.spi.BeanManager

che conterrà l’istanza del BeanManager, da poter usare per riscrivere il nostro metodo getBeanManager() in un contesto JSF come segue:

private static BeanManager getBeanManager() {
   ServletContext servletContext = (ServletContext) FacesContext
         .getCurrentInstance().getExternalContext().getContext();
   return (BeanManager) servletContext
         .getAttribute("javax.enterprise.inject.spi.BeanManager");
}

Conclusioni

Alla fine, l’idillio che sembrava infranto in quegli artefatti JSF (Convertitori e Validatori) o in tutti gli altri contesti non raggiungibili da CDI, trova una soluzione in modo centralizzato, indipendentemente dal nostro ambiente di runtime. In questo modo nessuno potrà più separarci dai nostri beniamini CDI bean… e vissero felici e contenti (eh si, chi s’accontenta gode)!

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+