adam bien's blog

EJB 3.1 BeanLocator - When Dependency Injection Is Not Enough 📎

Dependency Injection has also some disadvantages:

  1. It is static - the dependencies are resolved at start time.
  2. The service user has to live with the given contracts or conventions.
  3. 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"]