1. 概述

Enterprise Java Beans (EJB) 是 Java EE 规范 的核心组件,旨在简化分布式企业级应用的开发。EJB 的生命周期由应用服务器(如 JBoss WildFlyOracle GlassFish)管理。

EJB 提供了强大的编程模型,使企业级软件模块的实现更简单——因为事务处理、组件生命周期管理和依赖注入等非业务逻辑问题都交由应用服务器处理。

我们之前已发布两篇 EJB 基础概念的文章(链接1链接2),可作参考。本文将演示如何在 WildFly 上实现基础 EJB 模块,并通过 JNDI 从远程客户端调用 EJB。

2. 实现 EJB 模块

业务逻辑可通过以下方式实现:

  • 本地/远程业务接口(即本地/远程视图)
  • 或直接通过不实现任何接口的类(非视图接口)

⚠️ 关键区别

  • 本地接口:用于同一环境(同一 EAR/WAR 文件)的客户端访问
  • 远程接口:用于跨环境(不同 JVM 或应用服务器)的客户端访问

下面创建一个基础 EJB 模块,仅包含一个 Bean,其业务逻辑是将字符串转换为大写。

2.1. 定义远程业务接口

首先定义远程业务接口,使用 @Remote 注解(根据 EJB 3.x 规范,远程访问时必须标注):

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

2.2. 定义无状态 Bean

实现上述接口,注入业务逻辑:

@Stateless
public class TextProcessorBean implements TextProcessorRemote {
    public String processText(String text) {
        return text.toUpperCase();
    }
}

TextProcessorBean 是一个简单的 Java 类,标注 @Stateless 注解。无状态 Bean 不维护与客户端的会话状态(即使可跨请求保留实例状态),而有状态 Bean 会维护会话状态,因此创建成本更高。

本例中 Bean 无实例状态,适合设计为无状态。即使有状态,跨不同客户端请求使用也无意义。Bean 行为是确定性的(无副作用):输入字符串,返回大写版本。

2.3. Maven 依赖

添加 javaee-api 依赖,提供 Java EE 7 规范 API(含 EJB 所需 API):

<dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>7.0</version>
    <scope>provided</scope>
</dependency>

至此,一个基础可用的 EJB 模块已创建。为使其可供客户端使用,需将构件安装到本地 Maven 仓库。

2.4. 安装 EJB 模块到本地仓库

最简单方式是执行 Maven 生命周期命令:

mvn clean install

此命令将模块安装为 ejbmodule-1.0.jar(或 pom.xml 中定义的任意 artifact ID)。点击此处 了解 Maven 安装本地 JAR 的更多细节。

假设模块已正确安装,下一步是开发使用 TextProcessorBean API 的远程客户端。

3. 远程 EJB 客户端

客户端业务逻辑极简:

  1. 通过 JNDI 查找获取 TextProcessorBean 代理
  2. 调用代理的 processText() 方法

3.1. Maven 依赖

添加以下依赖使 EJB 客户端正常工作:

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

依赖说明:

  • javaee-api:基础 Java EE API
  • wildfly-ejb-client-bom必需! 用于在 WildFly 上执行远程 EJB 调用
  • ejbmodule:使客户端可用 EJB 模块

3.2. 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);
        }
    }
}

代码解析:

  • TextApplication 获取 Bean 代理并调用方法
  • EJBFactory 执行实际查找:
    1. 创建 JNDI InitialContext
    2. 设置 JNDI 参数
    3. 查找 Bean 代理

⚠️ 关键点

  • 使用 WildFly 专有的 ejb: 命名空间优化查找(延迟连接到服务器)
  • 若不用 ejb: 命名空间,将失去延迟连接优势,导致性能下降

3.3. 配置 EJB 上下文

客户端需知道连接的主机和端口。需通过 jboss-ejb-client.properties 文件配置 WildFly EJB 上下文(通常放在 src/main/resources):

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=admin
remote.connection.default.password=Admin@123

参数说明:

  • 连接配置(主机/端口/用户名/密码)
  • 默认禁用 SSL(生产环境建议启用)
  • 用户名/密码为模拟数据(实际需通过 add-user.sh/add-user.bat 工具 添加用户)

⚠️ 踩坑提醒:若连接需认证,务必提前在 WildFly 中添加用户,否则会报权限错误。

4. 总结

在 WildFly 上执行 EJB 查找很简单,只要严格遵循上述流程即可。所有示例代码可在 GitHub 获取:


原始标题:Introduction to EJB JNDI Lookup on WildFly