Adam Bien's Weblog

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"]


NEW workshop: Microservices with Java EE 7 and Java 8, January 26th, 2015, Airport Munich

A book about rethinking Java EE Patterns

Comments:

Hi Adam,

Nice utility. Couple of things though. The global name was standardized but I think the separator before bean business interfaces is '!' and not '#' - which is the Glassfish non-portable style.

Also, since the StringBuilder is a class instance variable, each call to asString produces a different value.

I used this with Guice and passed GlobalJNDIName to a Provider and was wondering why things suddenly stopped working. Maybe this was intended to be just an example, but since its a handly utility, why re-write yourself.

Posted by jukka on March 27, 2010 at 02:26 PM CET #

@Jukka,

if you like - send me the patch, or commit it directly to: http://kenai.com/projects/javaee-patterns/

I rewrote this utility from a project (because of NDAs etc.) for the book. This is the reason for the errors.

Thanks for feedback and testing!,

adam

Posted by 192.168.0.24 on March 27, 2010 at 02:46 PM CET #

Committed to Kenai.

Posted by jukka on March 27, 2010 at 09:54 PM CET #

Hi,

What if I need to lookups EJBs (3.1) from another remote Glassfish server? How do I specify the remote address and port EJB server port?

Thank you.

Posted by Xavier Callejas on March 08, 2012 at 09:49 PM CET #

Hi Adam,

wanting to do something like this -> http://docs.oracle.com/cd/B12166_01/web/B10321_01/jdbcejb.htm#1013849
- > EJB Remote Lookup Outside the Application

web: deploying servlet in server-1 with ipnr=1.
ejb: deploying ejb in server-2 with ipnr =2.

I have created 2 maven projects in netbeans: one web-projekt and one ejb-projekt.

I would like the coupling to be louse.

The EJB-project has 2-classes: CapitalBean and CapitalBeanRemote.

@Stateless
@EJB(name="java:global/CountryEnterpriseEJB/CapitalBean", beanInterface=CapitalBeanRemote.class)
public class CapitalBean implements CapitalBeanRemote {

The Web-project has 1 servlet.
bean = (CapitalBeanRemote) ic.lookup("java:global/CountryEnterpriseEJB/CapitalBean");

Web-project:
To be able to reference to the EJB-project and access the interface 'CapitalBeanRemote' i update my pom.xml will the dependency on that project.
Now that entire project ( jar-file ) is deployed in the 'WEB-INF.lib'-catalog.
So this is working now in one Server/Container ....
How do I declare that dependency in the WEB-project ( I would only like it to be aware of the remote interface ) ? And be able to update the EJB-project ( update some domain-classes later .... ) and deploy it in another server/container ( without changing the remote interface ).
Is the answer to break out the 'remote interface' in a third project and make that an dependecy to EJB and WEB ?

Bit confused here.
Any answers in your book 'real world ... patterns ' ?

Regards, Ingimar

Posted by Ingo on April 06, 2013 at 03:04 AM CEST #

Post a Comment:
  • HTML Syntax: NOT allowed
realworldpatterns.com
...the last 150 posts
...the last 10 comments
License