1. Overview
Enterprise Java Beans (EJB) are the core part of the Java EE specification, aimed at simplifying the development of distributed enterprise-level applications. The life-cycle of EJBs is handled by an application server, such as JBoss WildFly or Oracle GlassFish.
EJBs provide a robust programming model that facilitates the implementation of enterprise-level software modules, as it’s up to the application server to handle non-business logic related issues such as transaction handling, component lifecycle management or dependency injection.
Furthermore, we’ve already published two articles covering EJB’s basic concepts, so feel free to check them out here and here.
In this tutorial, we’ll show how to implement a basic EJB module on WildFly and call an EJB from a remote client via a JNDI.
2. Implementing the EJB Module
Business logic is implemented by either one or multiple local/remote business interfaces (also known as local/remote views) or directly through classes that don’t implement any interface (non-view interfaces).
It’s worth noting that local business interfaces are used when the bean is going to be accessed from clients that reside in the same environment, i.e. the same EAR or WAR file, whereas remote business interfaces are required when the bean will be accessed from a different environment, i.e. a different JVM or application server.
Let’s create a basic EJB module, which will be made up of just one bean. The bean’s business logic will be straightforward, limited to converting a given String to its uppercase version.
2.1. Defining a Remote Business Interface
Let’s first define one single remote business interface, decorated with the @Remote annotation. This is mandatory, according to the EJB 3.x specification, as the bean is going to be accessed from a remote client:
@Remote public interface TextProcessorRemote { String processText(String text); }
2.2. Defining a Stateless Bean
Next, let’s realize the business logic by implementing the aforementioned remote interface:
@Stateless public class TextProcessorBean implements TextProcessorRemote { public String processText(String text) { return text.toUpperCase(); } }
The TextProcessorBean class is a simple Java class, decorated with the @Stateless annotation.
Stateless beans, by definition, don’t maintain any conversational state with their clients, even when they can maintain instance state across different requests. Their counterpart, stateful beans, do preserve their conversational state, and such as they’re more expensive to create for the application server.
As in this case the above class doesn’t have any instance state, it can be made stateless. In case that it has a state, using it across different client requests wouldn’t make sense at all.
The bean’s behavior is deterministic, i.e., it has no side effects, as a well-designed bean should be: it just takes an input String and returns the uppercase version of it.
2.3. Maven Dependencies
Next, we need to add the javaee-api Maven artifact to the module, which provides all the Java EE 7 specification APIs, including the ones required for EJBs:
<dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency>
At this point, we’ve managed to create a basic, yet functional EJB module. To make it available to all potential clients, we have to add the artifact into our local Maven repository as a JAR file.
2.4. Installing the EJB Module into the Local Repository
There’re several methods for achieving this. The most straightforward one consists of executing the Maven lifecycle clean – install build phases:
mvn clean install
This command installs the EJB module as ejbmodule-1.0.jar (or any arbitrary artifact id specified in the pom.xml file), into our local repository. For further information on how to install a local JAR with Maven, check out this article.
Assuming that the EJB module has been correctly installed into our local repository, the next step is to develop a remote client application that makes use of our TextProcessorBean API.
3. Remote EJB Client
We’ll keep the remote EJB client’s business logic extremely simple: first, it performs a JNDI lookup to get a TextProcessorBean proxy. After that, it invokes the proxy’s processText() method.
3.1. Maven Dependencies
We need to include the following Maven artifacts for the EJB client to work as expected:
<dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.wildfly</groupId> <artifactId>wildfly-ejb-client-bom</artifactId> <version>10.1.0.Final</version> </dependency> <dependency> <groupId>com.beldung.ejbmodule</groupId> <artifactId>ejbmodule</artifactId> <version>1.0</version> </dependency>
While it’s pretty obvious why we include the javaee-api artifact, the inclusion of wildfly-ejb-client-bom is not. The artifact is required for performing remote EJB invocations on WildFly.
Last but not least, we need to make the previous EJB module available to the client, so that’s why we added the ejbmodule dependency too.
3.2. EJB Client Class
Considering that the EJB client calls a proxy of TextProcessorBean, we’ll be very pragmatic and name the client class TextApplication:
public class TextApplication { public static void main(String[] args) throws NamingException { TextProcessorRemote textProcessor = EJBFactory .createTextProcessorBeanFromJNDI("ejb:"); System.out.print(textProcessor.processText("sample text")); } private static class EJBFactory { private static TextProcessorRemote createTextProcessorBeanFromJNDI (String namespace) throws NamingException { return lookupTextProcessorBean(namespace); } private static TextProcessorRemote lookupTextProcessorBean (String namespace) throws NamingException { Context ctx = createInitialContext(); String appName = ""; String moduleName = "EJBModule"; String distinctName = ""; String beanName = TextProcessorBean.class.getSimpleName(); String viewClassName = TextProcessorRemote.class.getName(); return (TextProcessorRemote) ctx.lookup(namespace + appName + "/" + moduleName + "/" + distinctName + "/" + beanName + "!" + viewClassName); } private static Context createInitialContext() throws NamingException { Properties jndiProperties = new Properties(); jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory"); jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); jndiProperties.put(Context.PROVIDER_URL, "http-remoting://localhost:8080"); jndiProperties.put("jboss.naming.client.ejb.context", true); return new InitialContext(jndiProperties); } } }
Simply put, all that the TextApplication class does is retrieving the bean proxy and call its processText() method with a sample string.
The actual lookup is performed by the nested class EJBFactory, which first creates a JNDI InitialContext instance, then passes the required JNDI parameters to the constructor, and finally uses it for looking up the bean proxy.
Notice that the lookup is performed by using WildFly’s proprietary “ejb:” namespace. This optimizes the lookup process, as the client defers the connection to the server until the proxy is explicitly invoked.
It’s worth noting as well that it’s possible to lookup the bean proxy without resorting to the “ejb” namespace at all. However, we’d be missing all the additional benefits of lazy network connections, thus making the client a lot less performant.
3.3. Setting Up the EJB Context
The client should know what host and port to establish a connection with to perform the bean lookup. To this extent, the client requires setting up the proprietary WildFly EJB context, which is defined with the jboss-ejb-client.properties file placed in its classpath, usually under the src/main/resources folder:
endpoint.name=client-endpoint remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false remote.connections=default remote.connection.default.host=127.0.0.1 remote.connection.default.port=8080 remote.connection.default.connect.options.org.xnio.Options .SASL_POLICY_NOANONYMOUS=false remote.connection.default.username=myusername remote.connection.default.password=mypassword
The file is pretty self-explanatory, as it provides all parameters required for establishing a connection to WildFly, including the default number of remote connections, the default host, and port, and the user credentials. In this case, the connection isn’t encrypted, but it can be when SSL is enabled.
The last thing to take into account is that if the connection requires authentication, it’s necessary to add a user to WildFly via the add-user.sh/add-user.bat utility.
4. Conclusion
Performing EJB lookups on WildFly is straightforward, as long as we strictly adhere to the outlined process.
As usual, all the examples included in this article are available on GitHub here and here.