1. 概述

本文将演示如何集成Spring与远程企业级Java Bean(EJB)。我们将创建EJB及其远程接口,在JEE容器中运行它们,然后启动Spring应用,通过远程接口实例化Bean执行远程调用。

⚠️ 如果对EJB概念不熟悉,可参考入门文章(此处保留原文链接,实际使用时需替换为有效链接)

2. EJB环境搭建

2.1. EJB远程接口

先定义两个简单Bean——一个有状态(Stateful)和一个无状态(Stateless):

@Remote
public interface HelloStatefulWorld {
    int howManyTimes();
    String getHelloWorld();
}
@Remote
public interface HelloStatelessWorld {
    String getHelloWorld();
}

2.2. EJB实现类

实现远程接口:

@Stateful(name = "HelloStatefulWorld")
public class HelloStatefulWorldBean implements HelloStatefulWorld {

    private int howManyTimes = 0;

    public int howManyTimes() {
        return howManyTimes;
    }

    public String getHelloWorld() {
        howManyTimes++;
        return "Hello Stateful World";
    }
}
@Stateless(name = "HelloStatelessWorld")
public class HelloStatelessWorldBean implements HelloStatelessWorld {

    public String getHelloWorld() {
        return "Hello Stateless World!";
    }
}

✅ 有状态/无状态Bean的区别可参考入门文章

2.3. EJB容器配置

使用Wildfly实例运行应用。配置Maven插件部署到Wildfly:

<profile>
 <id>wildfly-runtime</id>
 <plugin>
     <groupId>org.wildfly.plugins</groupId>
     <artifactId>wildfly-maven-plugin</artifactId>
     <version>1.1.0.Alpha5</version>
     <configuration>
                <hostname>127.0.0.1</hostname>
                <port>9990</port>
                <username>admin</username>
                <password>admin1234!</password>
                <filename>${project.build.finalName}.jar</filename>
     </configuration>
 </plugin>
</profile>

2.4. 启动EJB服务

通过Maven命令启动容器:

mvn clean package cargo:run -Pwildfly-runtime

成功部署后日志会显示JNDI绑定信息:

java:global/ejb-remote-for-spring/HelloStatefulWorld!com.baeldung.ejb.tutorial.HelloStatefulWorld
java:app/ejb-remote-for-spring/HelloStatefulWorld!com.baeldung.ejb.tutorial.HelloStatefulWorld
java:module/HelloStatefulWorld!com.baeldung.ejb.tutorial.HelloStatefulWorld
java:jboss/exported/ejb-remote-for-spring/HelloStatefulWorld!com.baeldung.ejb.tutorial.HelloStatefulWorld
java:global/ejb-remote-for-spring/HelloStatefulWorld
java:app/ejb-remote-for-spring/HelloStatefulWorld
java:module/HelloStatefulWorld

java:global/ejb-remote-for-spring/HelloStatelessWorld!com.baeldung.ejb.tutorial.HelloStatelessWorld
java:app/ejb-remote-for-spring/HelloStatelessWorld!com.baeldung.ejb.tutorial.HelloStatelessWorld
java:module/HelloStatelessWorld!com.baeldung.ejb.tutorial.HelloStatelessWorld
java:jboss/exported/ejb-remote-for-spring/HelloStatelessWorld!com.baeldung.ejb.tutorial.HelloStatelessWorld
java:global/ejb-remote-for-spring/HelloStatelessWorld
java:app/ejb-remote-for-spring/HelloStatelessWorld
java:module/HelloStatelessWorld

3. Spring环境搭建

3.1. Maven依赖

添加Wildfly EJB客户端库和远程接口依赖:

<dependency>
    <groupId>org.wildfly</groupId>
    <artifactId>wildfly-ejb-client-bom</artifactId>
    <version>10.1.0.Final</version>
    <type>pom</type>
</dependency>
<dependency>
    <groupId>com.baeldung.spring.ejb</groupId>
    <artifactId>ejb-remote-for-spring</artifactId>
    <version>1.0.1</version>
    <type>ejb</type>
</dependency>

✅ 最新版本可在Maven仓库查询

3.2. 命名策略上下文

创建javax.naming.Context Bean用于远程查找:

@Bean   
public Context context() throws NamingException {
    Properties jndiProps = new Properties();
    jndiProps.put("java.naming.factory.initial", 
      "org.jboss.naming.remote.client.InitialContextFactory");
    jndiProps.put("jboss.naming.client.ejb.context", true);
    jndiProps.put("java.naming.provider.url", 
      "http-remoting://localhost:8080");
    return new InitialContext(jndiProps);
}

❌ 踩坑提醒:必须正确配置URL和命名工厂,否则连接会失败

3.3. JNDI查找模式

标准JNDI绑定模式:

${appName}/${moduleName}/${distinctName}/${beanName}!${viewClassName}

⚠️ 因我们部署的是简单jar(非ear)且未显式设置名称,所以appNamedistinctName为空

3.4. 构建Spring Bean

使用JNDI查找远程EJB:

@Bean
public HelloStatelessWorld helloStatelessWorld(Context context) 
  throws NamingException {
 
    return (HelloStatelessWorld) 
      context.lookup(this.getFullName(HelloStatelessWorld.class));
}
@Bean
public HelloStatefulWorld helloStatefulWorld(Context context) 
  throws NamingException {
 
    return (HelloStatefulWorld) 
      context.lookup(this.getFullName(HelloStatefulWorld.class));
}
private String getFullName(Class classType) {
    String moduleName = "ejb-remote-for-spring/";
    String beanName = classType.getSimpleName();
    String viewClassName = classType.getName();
    return moduleName + beanName + "!" + viewClassName;
}

❌ 关键点:JNDI绑定名称必须完全匹配,否则会抛出NamingException

4. 集成测试

在控制器中注入Bean测试集成:

@RestController
public class HomeEndpoint {
 
    // ...
 
    @GetMapping("/stateless")
    public String getStateless() {
        return helloStatelessWorld.getHelloWorld();
    }
    
    @GetMapping("/stateful")
    public String getStateful() {
        return helloStatefulWorld.getHelloWorld()
          + " called " + helloStatefulWorld.howManyTimes() + " times";
    }
}

启动Spring服务,成功日志:

EJBCLIENT000013: Successful version handshake completed

测试无状态Bean:

curl http://localhost:8081/stateless
Hello Stateless World!

测试有状态Bean(注意调用次数递增):

curl http://localhost:8081/stateful
Hello Stateful World called 1 times

curl http://localhost:8081/stateful
Hello Stateful World called 2 times

5. 总结

我们实现了Spring与EJB的集成,通过远程接口透明调用JEE容器中的EJB。关键步骤包括:

  1. ✅ 创建EJB远程接口和实现类
  2. ✅ 配置Wildfly容器并部署EJB
  3. ✅ 在Spring中配置JNDI上下文
  4. ✅ 使用JNDI模式查找远程Bean
  5. ✅ 通过Spring控制器验证集成效果

即使Spring广泛流行,EJB在企业环境中仍有应用。本示例展示了如何结合Jakarta EE的分布式能力与Spring的易用性。

完整代码见GitHub仓库(保留原文链接)


原始标题:Integration Guide for Spring and EJB