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