笔者在使用Tomee中EJB容器过程时发现,Tomee官方文档对EJB容器的远程调用的描述不够详细,同时网上的对EJB远程调用的资料也少之又少,本文旨在记录在使用Tomee中EJB容器远程调用过程中踩过的坑,以Tomee8.x为例。
Tomee官方文档:https://tomee.apache.org/tomee-8.0/docs/
1、前置步骤
修改Tomee配置文件system.properties,打开remote支持和设置白名单。
tomee.remote.support = true tomee.serialization.class.blacklist = - tomee.serialization.class.whitelist = * openejb.system.apps = true openejb.servicemanager.enabled = true
2、server端
server端通过maven打成对应的war包,最终打包好的名称为:ejb-remote-server-demo.war
公共接口:
@Remote public interface RemoteHello extends Serializable { String hello(String name); }
server端实现:
@Stateless(name = "statelessRemoteHelloServer") public class StatelessRemoteHelloServer implements RemoteHello { private static final Logger log = Logger.getLogger(StatelessRemoteHelloServer.class.getName()); private final String id = UUID.randomUUID().toString(); @Override public String hello(String name) { log.info("stateless sever receive: " + name); return "hello client " + name + ", stateless response by " + id; } }
3、client端调用
client端调用分为三种:
1、server端和client端部署在同一个Tomee实例中,通过client调用server。
2、server端部署在一个Tomee实例中,client通过main函数进行调用。
3、server端和client端分别部署在不同的Tomee实例中,通过client调用server。
3.1、同一Tomee实例调用
关键点在于JNDI注入的时候,lookup的名称为java:global/ejb-remote-server-demo/statelessRemoteHelloServer,其中ejb-remote-server-demo是server部署后的包名,statelessRemoteHelloServer是server端RemoteHello接口实现的名称。
因为是同一个Tomee实例中,即使用的是同一个EJB容器,不需要指定EJB容器的远程地址,相当于同实例中的跨应用调用。
示例代码如下:
@Stateful @WebServlet("/statelessClient") public class StatelessRemoteHelloClient extends HttpServlet { private static final Logger log = Logger.getLogger(StatelessRemoteHelloClient.class.getName()); protected void doGet(HttpServletRequest req, HttpServletResponse resp) { String name = req.getParameter("name"); try { InitialContext context = new InitialContext(); RemoteHello remoteHello = (RemoteHello) context.lookup("java:global/ejb-remote-server-demo/statelessRemoteHelloServer"); String res = remoteHello.hello(name); log.info("stateless server response: " + res); PrintWriter writer = resp.getWriter(); writer.println(res); } catch (Exception e) { throw new RuntimeException(e); } } }
3.2、通过main函数调用
client通过main函数调用,不同JVM实例调用,需要指定EJB的context factory为org.apache.openejb.client.RemoteInitialContextFactory,provider.url为远程server端的EJB容器的地址,例如:http://localhost:8081/tomee/ejb
官方文档中给出的示例使用的provider.url为ejbd://25.14.3.92:4201,其中协议是ejbd,如果使用ejbd调用server端时,会提示以下错误:
Tomee在启动的时候输出日志会输出EJB容器的调用地址:
官方remote ejb文档:https://tomee.apache.org/tomee-8.0/docs/remote-server.html
JNDI注入lookup查找名称 java:global/ejb-remote-server-demo/statelessRemoteHelloServer!com.example.RemoteHello,其中statelessRemoteHelloServer为server端RemoteHello实现类的名称,com.example.RemoteHello为RemoteHello接口的全限定类名。
示例代码如下:
public static void main(String[] args) throws Exception{ Properties p = new Properties(); p.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.RemoteInitialContextFactory"); p.put(Context.PROVIDER_URL, "http://localhost:8081/tomee/ejb"); InitialContext context = new InitialContext(p); RemoteHello remoteHello = (RemoteHello) context.lookup("java:global/ejb-remote-server-demo/statelessRemoteHelloServer!com.example.RemoteHello"); String res = remoteHello.hello("test"); log.info("stateful server response: " + res); }
3.3、不同Tomee实例调用
client端打成war后部署到Tomee中,通过servlet接口的形式调用,其中实现细节和main函数调用一致,不同的是JNDI注入lookup查找时名称不一样,可以通过global/ejb-remote-server-demo/statelessRemoteHelloServer!com.example.RemoteHello和statelessRemoteHelloServer两种方式都可以。
@WebServlet("/differentTomee") public class DifferentTomeeInstanceServlet extends HttpServlet { private static final Logger log = Logger.getLogger(DifferentTomeeInstanceServlet.class.getName()); protected void doGet(HttpServletRequest req, HttpServletResponse resp) { String name = req.getParameter("name"); try { Properties p = new Properties(); p.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.RemoteInitialContextFactory"); p.put(Context.PROVIDER_URL, "http://localhost:8081/tomee/ejb"); InitialContext context = new InitialContext(p); // global/ejb-remote-server-demo/statelessRemoteHelloServer!com.example.RemoteHello // statelessRemoteHelloServer RemoteHello remoteHello = (RemoteHello) context.lookup("statelessRemoteHelloServer"); String res = remoteHello.hello(name); log.info("stateful server response: " + res); PrintWriter writer = resp.getWriter(); writer.println(res); } catch (Exception e) { log.severe("error: " + e); throw new RuntimeException(e); } } }