Like you saw in the previous sections, in case of scoped EJB client contexts, the EJB client context is tied to the JNDI context. It's very important to understand how the lifecycle of the EJB client context works in such cases. Especially since any EJB client context is almost always backed by connections to the server. Not managing the EJB client context lifecycle correctly can lead to connection leaks in some cases.
When you create a scoped EJB client context, the EJB client context connects to the server(s) listed in the JNDI properties. An internal implementation detail of this logic includes the ability of the EJB client context to cache connections based on certain internal algorithm it uses. The algorithm itself isn't publicly documented (yet) since the chances of it changing or even removal shouldn't really affect the client application and instead it's supposed to be transparent to the client application.
The connections thus created for a EJB client context are kept open as long as the EJB client context is open. This allows the EJB client context to be usable for EJB invocations. The connections associated with the EJB client context are closed when the EJB client context itself is closed.
How to close EJB client contexts?
The answer to that is simple. Use the close() method on the appropriate EJB client context.
How to close scoped EJB client contexts?
The answer is the same, use the close() method on the EJB client context. But the real question is how do you get the relevant scoped EJB client context which is associated with a JNDI context. Before we get to that, it's important to understand how the ejb: JNDI namespace that's used for EJB lookups and how the JNDI context (typically the InitialContext that you see in the client code) are related. The JNDI API provided by Java language allows "URL context factory" to be registered in the JNDI framework (see this for details http://docs.oracle.com/javase/jndi/tutorial/provider/url/factory.html). Like that documentation states, the URL context factory can be used to resolve URL strings during JNDI lookup. That's what the ejb: prefix is when you do a remote EJB lookup. The ejb: URL string is backed by a URL context factory.
Internally, when a lookup happens for a ejb: URL string, a relevant javax.naming.Context is created for that ejb: lookup. Let's see some code for better understanding:
// JNDI context "A"
Context jndiCtx = new InitialContext(props);
// Now let's lookup a EJB
MyBean bean = jndiCtx.lookup("ejb:app/module/distinct/bean!interface");
So we first create a JNDI context and then use it to lookup an EJB. The bean lookup using the ejb: JNDI name, although, is just one statement, involves a few more things under the hood. What's actually happening when you lookup that string is that a separate javax.naming.Context gets created for the ejb: URL string. This new javax.naming.Context is then used to lookup the rest of the string in that JNDI name.
Let's break up that one line into multiple statements to understand better:
// Remember, the ejb: is backed by a URL context factory which returns a Context for the ejb: URL (that's why it's called a context factory)
final Context ejbNamingContext = (Context) jndiCtx.lookup("ejb:");
// Use the returned EJB naming context to lookup the rest of the JNDI string for EJB
final MyBean bean = ejbNamingContext.lookup("app/module/distinct/bean!interface");
As you see above, we split up that single statement into a couple of statements for explaining the details better. So as you can see when the ejb: URL string is parsed in a JNDI name, it gets hold of a javax.naming.Context instance. This instance is different from the one which was used to do the lookup (jndiCtx in this example). This is an important detail to understand (for reasons explained later). Now this returned instance is used to lookup the rest of the JNDI string ("app/module/distinct/bean!interface"), which then returns the EJB proxy. Irrespective of whether the lookup is done in a single statement or multiple parts, the code works the same. i.e. an instance of javax.naming.Context gets created for the ejb: URL string.
So why am I explaining all this when the section is titled "How to close scoped EJB client contexts"? The reason is because client applications dealing with scoped EJB client contexts which are associated with a JNDI context would expect the following code to close the associated EJB client context, but will be surprised that it won't:
final Properties props = new Properties();
// mark it for scoped EJB client context
props.put("org.jboss.ejb.client.scoped.context","true");
// add other properties
props.put(....);
...
Context jndiCtx = new InitialContext(props);
try {
final MyBean bean = jndiCtx.lookup("ejb:app/module/distinct/bean!interface");
bean.doSomething();
} finally {
jndiCtx.close();
}
Applications expect that the call to jndiCtx.close() will effectively close the EJB client context associated with the JNDI context. That doesn't happen because as explained previously, the javax.naming.Context backing the ejb: URL string is a different instance than the one the code is closing. The JNDI implementation in Java, only just closes the context on which the close was called. As a result, the other javax.naming.Context that backs the ejb: URL string is still not closed, which effectively means that the scoped EJB client context is not closed too which then ultimately means that the connection to the server(s) in the EJB client context are not closed too.
So now let's see how this can be done properly. We know that the ejb: URL string lookup returns us a javax.naming.Context. All we have to do is keep a reference to this instance and close it when we are done with the EJB invocations. So here's how it's going to look:
final Properties props = new Properties();
// mark it for scoped EJB client context
props.put("org.jboss.ejb.client.scoped.context","true");
// add other properties
props.put(....);
...
Context jndiCtx = new InitialContext(props);
Context ejbRootNamingContext = (Context) jndiCtx.lookup("ejb:");
try {
final MyBean bean = ejbRootNamingContext.lookup("app/module/distinct/bean!interface"); // rest of the EJB jndi lookup string
bean.doSomething();
} finally {
try {
// close the EJB naming JNDI context
ejbRootNamingContext.close();
} catch (Throwable t) {
// log and ignore
}
try {
// also close our other JNDI context since we are done with it too
jndiCtx.close();
} catch (Throwable t) {
// log and ignore
}
}
As you see, we changed the code to first do a lookup on just the "ejb:" string to get hold of the EJB naming context and then used that ejbRootNamingContext instance to lookup the rest of the EJB JNDI name to get hold of the EJB proxy. Then when it was time to close the context, we closed the ejbRootNamingContext (as well as the other JNDI context). Closing the ejbRootNamingContext ensures that the scoped EJB client context associated with that JNDI context is closed too. Effectively, this closes the connection(s) to the server(s) within that EJB client context.
Can that code be simplified a bit?
If you are using that JNDI context only for EJB invocations, then yes you can get rid of some instances and code from the above code. You can change that code to:
final Properties props = new Properties();
// mark it for scoped EJB client context
props.put("org.jboss.ejb.client.scoped.context","true");
// add other properties
props.put(....);
...
Context ejbRootNamingContext = (Context) new InitialContext(props).lookup("ejb:");
try {
final MyBean bean = ejbRootNamingContext.lookup("app/module/distinct/bean!interface"); // rest of the EJB lookup string
bean.doSomething();
} finally {
try {
// close the EJB naming JNDI context
ejbRootNamingContext.close();
} catch (Throwable t) {
// log and ignore
}
}
Notice that we no longer hold a reference to 2 JNDI contexts and instead just keep track of the ejbRootNamingContext which is actually the root JNDI context for our "ejb:" URL string. Of course, this means that you can only use this context for EJB lookups or any other EJB related JNDI lookups. So it depends on your application and how it's coded.
Can't the scoped EJB client context be automatically closed by the EJB client API when the JNDI context is no longer in scope (i.e. on GC)?
That's one of the common questions that gets asked. No, the EJB client API can't take that decision. i.e. it cannot automatically go ahead and close the scoped EJB client context by itself when the associated JNDI context is eligible for GC. The reason is simple as illustrated by the following code:
void doEJBInvocation() {
final MyBean bean = lookupEJB();
bean.doSomething();
bean.doSomeOtherThing();
... // do some other work
bean.keepDoingSomething();
}
MyBean lookupEJB() {
final Properties props = new Properties();
// mark it for scoped EJB client context
props.put("org.jboss.ejb.client.scoped.context","true");
// add other properties
props.put(....);
...
Context ejbRootNamingContext = (Context) new InitialContext(props).lookup("ejb:");
final MyBean bean = ejbRootNamingContext.lookup("app/module/distinct/bean!interface"); // rest of the EJB lookup string
return bean;
}
As you can see, the doEJBInvocation() method first calls a lookupEJB() method which does a lookup of the bean using a JNDI context and then returns the bean (proxy). The doEJBInvocation() then uses that returned proxy and keeps doing the invocations on the bean. As you might have noticed, the JNDI context that was used for lookup (i.e. the ejbRootNamingContext) is eligible for GC. If the EJB client API had closed the scoped EJB client context associated with that JNDI context, when that JNDI context was garbage collected, then the subsequent EJB invocations on the returned EJB (proxy) would start failing in doEJBInvocation() since the EJB client context is no longer available.
That's the reason why the EJB client API doesn't automatically close the EJB client context.