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 staticT getBean(Class clazz) { BeanManager bm = getBeanManager(); Bean bean = (Bean ) bm.getBeans(clazz).iterator().next(); CreationalContext 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:
org.jboss.weld.environment.servlet.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)!