Introduction to EJB JNDI Lookup on WildFly Application Server – WildFly应用服务器上的EJB JNDI查询介绍

最后修改: 2017年 9月 1日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

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.

企业级Java Bean(EJB)是Java EE规范的核心部分,旨在简化分布式企业级应用程序的开发。EJB的生命周期由应用服务器处理,例如JBoss WildFlyOracle 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.

EJBs提供了一个强大的编程模型,促进了企业级软件模块的实现,因为要由应用服务器来处理与业务逻辑无关的问题,如事务处理、组件生命周期管理或依赖注入。

Furthermore, we’ve already published two articles covering EJB’s basic concepts, so feel free to check them out here and here.

此外,我们已经发表了两篇涵盖EJB基本概念的文章,请随时查看这里这里

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.

在本教程中,我们将展示如何在WildFly上实现一个基本的EJB模块,并通过JNDI从远程客户端调用一个EJB。

2. Implementing the EJB Module

2.实现EJB模块

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.

值得注意的是,当Bean将被位于同一环境中的客户端访问时,即同一EAR或WAR文件,就会使用本地业务接口,而当Bean将从不同的环境,即不同的JVM或应用服务器访问时,就需要远程业务接口。

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.

让我们创建一个基本的EJB模块,它将由一个Bean组成。这个Bean的业务逻辑将是简单明了的,仅限于将一个给定的String转换为大写字母。

2.1. Defining a Remote Business Interface

2.1.定义一个远程业务接口

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注解来装饰。根据EJB 3.x规范,这是必须的,因为Bean将被从一个远程客户端访问。

@Remote
public interface TextProcessorRemote {
    String processText(String text);
}

2.2. Defining a Stateless Bean

2.2.定义一个无状态 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.

TextProcessorBean类是一个简单的Java类,用@Stateless注解进行了装饰。

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.

根据定义,无状态Bean不与他们的客户保持任何对话状态,即使他们可以在不同的请求中保持实例状态。它们的对应物,有状态Bean,确实保留了它们的对话状态,而且对于应用服务器来说,它们的创建成本更高。

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.

这个Bean的行为是确定的,也就是说,它没有任何副作用,正如一个设计良好的Bean应该有的那样:它只是接受一个输入String,并返回它的大写版本。

2.3. Maven Dependencies

2.3.Maven的依赖性

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:

接下来,我们需要将javaee-api Maven工件添加到模块中,它提供了所有Java EE 7规范的API,包括EJB所需的API。

<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.

至此,我们已经成功创建了一个基本的、功能性的EJB模块。为了让所有潜在客户都能使用该模块,我们必须将该模块作为一个JAR文件添加到本地Maven仓库。

2.4. Installing the EJB Module into the Local Repository

2.4.将EJB模块安装到本地存储库中

There’re several methods for achieving this. The most straightforward one consists of executing the Maven lifecycle clean – install build phases:

有几种方法可以做到这一点。最直接的方法是执行Maven生命周期的清洁-安装构建阶段。

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.

该命令将EJB模块作为ejbmodule-1.0.jar(或在 pom.xml文件中指定的任意工件ID)安装到我们的本地仓库。关于如何用Maven安装本地JAR的进一步信息,请查看这篇文章

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.

假设EJB模块已经正确地安装到我们的本地资源库中,下一步就是开发一个远程客户端应用程序,利用我们的TextProcessorBean API。

3. Remote EJB Client

3.远程EJB客户端

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.

我们将保持远程EJB客户端的业务逻辑极其简单:首先,它执行JNDI查询以获得一个TextProcessorBean代理。然后,它调用代理的processText()方法。

3.1. Maven Dependencies

3.1.Maven的依赖性

We need to include the following Maven artifacts for the EJB client to work as expected:

为了让EJB客户端如期工作,我们需要包含以下Maven构件。

<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.

虽然我们包含javaee-api工件的原因非常明显,但包含wildfly-ejb-client-bom却不是。在WildFly上执行远程EJB调用时,需要该神器。

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.

最后但并非最不重要的是,我们需要使之前的EJB模块对客户端可用,所以这就是为什么我们也添加了ejbmodule依赖性。

3.2. EJB Client Class

3.2.EJB客户端类

Considering that the EJB client calls a proxy of TextProcessorBean, we’ll be very pragmatic and name the client class TextApplication:

考虑到EJB客户端调用TextProcessorBean的代理,我们将非常务实地将客户端类命名为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.

简单地说,TextApplication类所做的就是检索Bean代理,并用一个样本字符串调用其 processText()方法。

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.

实际的查找是由嵌套类EJBFactory执行的,它首先创建一个JNDI InitialContext实例,然后将所需的JNDI参数传递给构造函数,最后用它来查找bean代理。

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.

请注意,查询是通过使用WildFly专有的 “ejb: “命名空间进行的。这优化了查找过程,因为客户端会推迟与服务器的连接,直到代理被明确调用。

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.

值得注意的是,我们也可以在不使用 “ejb “命名空间的情况下查询bean代理。然而,我们将失去懒惰网络连接的所有额外好处,从而使客户端的性能大打折扣

3.3. Setting Up the EJB Context

3.3.设置EJB上下文

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:

客户端应该知道用什么主机和端口来建立连接以执行Bean查找。在这种程度上,客户端需要设置专有的WildFly EJB上下文,该上下文是通过放置在其classpath(通常在src/main/resources文件夹下)的jboss-ejb-client.properties文件定义的。

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.

该文件是相当不言自明的,因为它提供了与WildFly建立连接所需的所有参数,包括默认的远程连接数、默认的主机和端口,以及用户凭证。在这种情况下,连接不是加密的,但当启用SSL时,它可以是加密的。

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.

最后要考虑的是,如果连接需要认证,就有必要通过add-user.sh/add-user.bat实用程序添加一个用户到WildFly。

4. Conclusion

4.结论

Performing EJB lookups on WildFly is straightforward, as long as we strictly adhere to the outlined process.

在WildFly上执行EJB查询是很简单的,只要我们严格遵守列出的过程。

As usual, all the examples included in this article are available on GitHub here and here.

像往常一样,本文中包含的所有例子都可以在GitHub上找到这里这里