EJB 3.1 BeanLocator - When Dependency Injection Is Not Enough 📎
Dependency Injection has also some disadvantages:
- It is static - the dependencies are resolved at start time.
- The service user has to live with the given contracts or conventions.
- DI is only available for certain classes which are managed by a container.
In rare cases you will have to use the BeanLocator (ServiceLocator 2.0). It encapsulates the JNDI and makes the lookup easier. In EJB 3.1 the JNDI-names were standardized and can be reliably derived from the ear, ejb and EJB-class/interface names. Knowing that, you can go even further and use a builder pattern to construct the names.
public class BeanLocator {
public static class GlobalJNDIName {
private StringBuilder builder;
private final static String PREFIX = "java:global";
//some constants omitted...
public GlobalJNDIName() {
//loading the configuration
}
public GlobalJNDIName withAppName(String appName) {
this.appName = appName;
return this;
}
public GlobalJNDIName withModuleName(String moduleName) {
this.moduleName = moduleName;
return this;
}
public GlobalJNDIName withBeanName(String beanName) {
this.beanName = beanName;
return this;
}
//some builder methods omitted
String computeBeanName(Class beanClass) {
//some logic
}
private boolean isNotEmpty(String name){
return (name != null && !name.isEmpty());
}
public String asString() {
//construction
}
public T locate(Class clazz) {
return BeanLocator.lookup(clazz, asString());
}
public Object locate() {
return BeanLocator.lookup(asString());
}
}
/**
*
* @param clazz the type (Business Interface or Bean Class)
* @param jndiName the global JNDI name with the pattern: java:global[/]//#
* @return The local or remote reference to the bean.
*/
public static T lookup(Class clazz, String jndiName) {
Object bean = lookup(jndiName);
return clazz.cast(PortableRemoteObject.narrow(bean, clazz));
}
public static Object lookup(String jndiName) {
Context context = null;
try {
context = new InitialContext();
return context.lookup(jndiName);
} catch (NamingException ex) {
throw new IllegalStateException("Cannot connect to bean: " + jndiName + " Reason: " + ex, ex.getCause());
} finally {
try {
context.close();
} catch (NamingException ex) {
throw new IllegalStateException("Cannot close InitialContext. Reason: " + ex, ex.getCause());
}
}
}
}
The module and ear names are static and stable. You can specify them in a property file (global.jndi):
#Configuration for the application-wide defaults
module.name=BeanLocatorModule
application.name=BeanLocatorApp
Alternatively, you could specify everything you need with the builder pattern:
@Test
public void jndiName() {
String expected = "java:global/appName/moduleName/beanName#java.io.Serializable";
String actual = new BeanLocator.GlobalJNDIName().
withAppName("appName").
withModuleName("moduleName").
withBeanName("beanName").withBusinessInterface(Serializable.class).asString();
assertEquals(expected, actual);
}
The BeanLocator could be used in Servlets as following:
public class TestServlet extends HttpServlet {
private TestSingleton testSingleton;
@Override
public void init() throws ServletException {
this.testSingleton = (TestSingleton) new BeanLocator.GlobalJNDIName().
withBeanName(TestSingleton.class).
locate();
}
The whole project (BeanLocator) with tests was pushed into: http://kenai.com/projects/javaee-patterns/. It was tested with Glassfish v3 Preview and Netbeans 6.5/6.7.1.
Interesting: the parsing of global.jndi is faster than accessing the annotations the first time.
[You will find a more detailed explanation of the BeanLocator in the book "Real World Java EE Patterns - Rethinking Best Practices", page: 217, Chapter "Infrastructural Patterns And Utilities"]