0

关于从独立应用程序远程访问 JBoss EJB 有大量令人沮丧的错误信息(更好的描述 - “关闭但没有雪茄”)。一天多来,我一直在用头撞墙,但没有成功。

我正在尝试将 EJB 从 WebLogic 移植到 JBoss,它由运行在另一台服务器上的独立应用程序调用。

我一直在这里这里和其他几个地方寻找各种“解决方案”来解决我的问题,但没有成功。我已经尝试阅读官方文档,它希望我安装一个基于 Maven 的“快速入门”,它可能适合我的情况,也可能不适合我的情况,到目前为止我决定不去追求。(我的项目不是用 Maven 构建的,它使用 Gradle,但我有理由确定我已经成功部署了所有正确的依赖项)。

我有一个有状态的 EJB 部署在 EAR 内的 WAR 中(以前将其简单地部署在 WAR 中的实现没有帮助)。

我这样配置客户端:

        public InitialContext createInitialContext() throws NamingException {
            Properties prop = new Properties();
            prop.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");

            prop.put(Context.INITIAL_CONTEXT_FACTORY,
                    "org.jboss.naming.remote.client.InitialContextFactory");

            prop.put(Context.PROVIDER_URL, purl);
            prop.put(Context.SECURITY_PRINCIPAL, "myusername");
            prop.put(Context.SECURITY_CREDENTIALS, "mypassword");
            prop.put("jboss.naming.client.ejb.context", false);

            return new InitialContext(prop);            
        }

        public void closeContext(Context context) throws NamingException {
            if (context != null) {
                context.close();
            }
        }

        private String getJndiName(
                String prefix, 
                String appName,
                String moduleName,
                String distinctName,
                String beanName,
                Class viewClass, 
                boolean stateful) 
        {
            StringBuilder builder = new StringBuilder();
            if (prefix != null && prefix.length() > 0) {
                builder.append(prefix).append(':');
            }
            builder.append(appName)
                    .append('/')
                    .append(moduleName)
                    .append('/')
                    .append(distinctName)
                    .append('/')
                    .append(beanName).append('!')
                    .append(viewClass.getName());
            if (stateful) {
                builder.append("?stateful");
            }
            return builder.toString();
        }
        public Object lookup(Context context) throws NamingException {

            final String prefix = "ejb"; 
            final String appName = "myearname";
            final String moduleName = "mywarname";
            final String distinctName = "";
            final String beanName = "MyBean";
            final Class viewClass = MyBeanInterface.class;


            String jndi = getJndiName(prefix, appName, moduleName, distinctName, beanName, viewClass, true);


            return context.lookup(jndi);
        }

请注意,没有提供任何“不同的名称”,因为不需要。“distinct name”应该是可选的:所有这些都被调用:

                MyBeanInterface sstatus = null;
                try {
                    ctx = createInitialContext();
                    sstatus = (MyBeanInterface) lookup(ctx);

                } catch (Exception ex) {
                     ...
                }

调用此代码时,会产生以下错误消息:

Caused by: java.lang.IllegalStateException: EJBCLIENT000024: No EJB receiver available for handling [appName:SockTransport, moduleName:SockTransport, distinctName:] combination
        at org.jboss.ejb.client.EJBClientContext.requireEJBReceiver(EJBClientContext.java:873) ~[ttjd.jar:?]
        at org.jboss.ejb.client.EJBClient.createSessionWithPossibleRetries(EJBClient.java:222) ~[ttjd.jar:?]
        at org.jboss.ejb.client.EJBClient.createSession(EJBClient.java:202) ~[ttjd.jar:?]
        at org.jboss.ejb.client.naming.ejb.EjbNamingContext.doCreateProxy(EjbNamingContext.java:227) ~[ttjd.jar:?]
        at org.jboss.ejb.client.naming.ejb.EjbNamingContext.createEjbProxy(EjbNamingContext.java:204) ~[ttjd.jar:?]

使用上面的代码,我提供的 JNDI 名称是 ejb:myearname/mywarname//MyBean!com.whatever.my.package.MyBeanInterface. 请注意由缺少 distinctName 引起的双斜杠。我可以并且已经重新调整了此代码以代替生成ejb:myearname/mywarname/MyBean!com.whatever.my.package.MyBeanInterface,这没有什么区别。

坦率地说,我认为这个错误信息是一个红鲱鱼。 我怀疑我的设置还有一些其他问题没有在这个界面上被捕获和破坏。我不认为独特的名称或缺乏与问题有任何关系。我认为这只是他们记录无法查找的对象的方式。

在我想出如何添加一个无用的“独特名称”以使 JBOSS 高兴之前可能是徒劳的,有人可以大胆猜测一下真正的问题可能是什么吗?

更新:

@Steve_C 的建议很有启发性,但我仍然没有让它们发挥作用。他在最初的上下文创建中留下了几点:

  • Context.URL_PKG_PREFIXES
  • Context.INITIAL_CONTEXT_FACTORY
  • “jboss.naming.client.ejb.context”

他引用的资源中提到了这些——顺便说一句,非常方便。

所以我添加了这些,我的 createInitialContext 方法现在看起来像这样:

    public InitialContext createInitialContext() throws NamingException {
        Properties prop = new Properties();
        prop.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
        prop.put(Context.INITIAL_CONTEXT_FACTORY,
                "org.jboss.naming.remote.client.InitialContextFactory");
        prop.put(Context.PROVIDER_URL, "http-remoting://{server-ip}:{server-port});
        prop.put("jboss.naming.client.ejb.context", true);
        return new InitialContext(prop);            
    }

为什么当我已经在 jboss-ejb-client.properties 文件中提供了 server-ip 和 server-port 时需要 PROVIDER_URL 仍然是神秘的,但它有所作为。

将这三个项目添加到我的初始上下文环境中,现在我收到一条不同的错误消息(EJBCLIENT000025 而不是 EJBCLIENT000024):

java.lang.IllegalStateException: EJBCLIENT000025: No EJB receiver available for handling [appName:SockTransport, moduleName:SockTransport, distinctName:] combination for invocation context org.jboss.ejb.client.EJBClientInvocationContext@67f639d3
        at org.jboss.ejb.client.EJBClientContext.requireEJBReceiver(EJBClientContext.java:798) ~[ttjd.jar:?]
        at org.jboss.ejb.client.ReceiverInterceptor.handleInvocation(ReceiverInterceptor.java:128) ~[ttjd.jar:?]
        at org.jboss.ejb.client.EJBClientInvocationContext.sendRequest(EJBClientInvocationContext.java:186) ~[ttjd.jar:?]
        at org.jboss.ejb.client.EJBInvocationHandler.sendRequestWithPossibleRetries(EJBInvocationHandler.java:255) ~[ttjd.jar:?]
        at org.jboss.ejb.client.EJBInvocationHandler.doInvoke(EJBInvocationHandler.java:200) ~[ttjd.jar:?]
        at org.jboss.ejb.client.EJBInvocationHandler.doInvoke(EJBInvocationHandler.java:183) ~[ttjd.jar:?]
    at org.jboss.ejb.client.EJBInvocationHandler.invoke(EJBInvocationHandler.java:146) ~[ttjd.jar:?]
    at com.sun.proxy.$Proxy20.create(Unknown Source) ~[?:?]

我想这算是进步,但我发现这比它需要的更困难。我想知道这些新属性是否需要在属性文件中,但官方文档很清楚地说它们不需要。

4

2 回答 2

1

最灵活的 WildFly/JBossEAP 远程 EJB 查找和调用可以如下完成:

创建一个jboss-ejb-client.properties必须在客户端类路径上的文件:

remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false
remote.connections=default
remote.connection.default.host=<ip of jboss eap host>
remote.connection.default.port = 8080
remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false

EJBCLIENT000024: No EJB receiver available for handling 错误消息是丢失 jboss-ejb-client.properties 文件症状。

创建一个InitialContext

    Properties jndiProps = new Properties();
    jndiProps.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
    Context ctx = new InitialContext(jndiProps);

请注意,不需要其他属性。

查找 bean 并调用它:

    ServiceLogic beanRemoteInterface = (ServiceLogic) ctx.lookup("ejb:/WhizBangSessionEJB/WhizBangSessionEJB!com.whatever.hostinterface.ServiceLogic?stateful");
    String bar = beanRemoteInterface.sayHello();
    System.out.println("Remote Foo bean returned " + bar);

请注意 ?stateful 有状态 EJB 所需的 JNDI 名称末尾的 。

输出:

Jan 11, 2017 11:07:46 PM org.jboss.ejb.client.EJBClient <clinit>
INFO: JBoss EJB Client version 2.1.4.Final
Jan 11, 2017 11:07:46 PM org.xnio.Xnio <clinit>
INFO: XNIO version 3.4.0.Final
Jan 11, 2017 11:07:46 PM org.xnio.nio.NioXnio <clinit>
INFO: XNIO NIO Implementation Version 3.4.0.Final
Jan 11, 2017 11:07:46 PM org.jboss.remoting3.EndpointImpl <clinit>
INFO: JBoss Remoting version 4.0.21.Final
Jan 11, 2017 11:07:46 PM org.jboss.ejb.client.remoting.VersionReceiver handleMessage
INFO: EJBCLIENT000017: Received server version 2 and marshalling strategies [river]
Jan 11, 2017 11:07:46 PM org.jboss.ejb.client.remoting.RemotingConnectionEJBReceiver associate
INFO: EJBCLIENT000013: Successful version handshake completed for receiver context EJBReceiverContext{clientContext=org.jboss.ejb.client.EJBClientContext@29ca901e, receiver=Remoting connection EJB receiver [connection=org.jboss.ejb.client.remoting.ConnectionPool$PooledConnection@5649fd9b,channel=jboss.ejb,nodename=steves-mbp]} on channel Channel ID ecac0ca6 (outbound) of Remoting connection 6536e911 to /192.168.12.6:8080 of endpoint "config-based-ejb-client-endpoint" <520a3426>
Remote Foo bean returned hello

可以在通过 JNDI-EJB 客户端 API 或远程命名项目的远程 EJB 调用中找到更多信息。

更多示例代码可以在Wildfly/quickstart/ejb-remote的 QuickStart 存储库中找到

PS。如果你真的想设置,distinct-name那么你需要向jboss-ejb3.xml你的 EJB jar 添加一个文件,其中包含:

<jboss:ejb-jar xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
               xmlns="http://www.jboss.com/xml/ns/javaee"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-ejb3-2_0.xsd"
               version="3.1"
               impl-version="2.0">

    <distinct-name>something-distinct</distinct-name>
</jboss:ejb-jar>

动态 EJB 客户端属性

如果您需要能够jboss-ejb-client.properties 动态配置,那么最简单的解决方案是动态生成此文件,可能在客户端初始化时。

  1. jboss.ejb.client.properties.file.path系统属性设置为指向安全的可写文件系统位置。一个不安全的例子可能是这样的

    -Djboss.ejb.client.properties.file.path=/tmp/whizbang-ejb.properties

    或者

    System.setProperty("jboss.ejb.client.properties.file.path", "/tmp/whizbang-ejb.properties");

  2. jboss.ejb.client.properties.file.path根据文件描述的格式,生成一个以定义的字符串命名的属性jboss-ejb-client.properties文件。

  3. 继续创建 InitialContext

还有其他替代方法涉及破解提供的jboss-ejb-client 代码。但是,您需要记住这是 LGPL 代码,您和您的公司需要公开您的黑客攻击。

于 2017-01-11T12:19:52.087 回答
0

在分享我学到的东西之前,我想向@Steve_C 大声疾呼,他在帮助我方面远远超出了职责范围,包括一个冗长的聊天会话。如果有人想知道,顺便说一句,他不是我。

这意味着没有不尊重@Steve_C 或他的回答(我认为这是有用的),这里还有更多要说的,因为我从非常痛苦的经历中学到了。

以下是我学到的一些东西:

1)必须有一个jboss-ejb-client.properties文件。
2) 该文件可以位于类路径上,也可以在以下 System 属性指定的位置中指定,我在调用 InitialContext 构造函数之前设置了该属性:

    System.setProperty("jboss.ejb.client.properties.file.path", "/path/to/properties/file");
    return new InitialContext(prop);    

3) 此文件必须命名连接:

remote.connections=conn1,conn2

4) 对于上述属性中命名的每个连接,必须将主机和端口条目存储在属性文件中

remote.connection.conn1.host=10.0.0.1
remote.connection.conn1.port=8080
remote.connection.conn2.host=10.0.0.2
remote.connection.conn2.port=8080

5) 对于每个命名的连接,还必须指定某种身份验证方法,要么 a)

remote.connection.conn1.username=user1
remote.connection.conn1.password=topSecret
remote.connection.conn2.username=user2
remote.connection.conn2.password=open_sesame

或 b)

remote.connection.conn1.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false
remote.connection.conn2.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false

这从字面上表示不允许匿名无密码调用。(让我们听到双重否定!)我想理论上可以使用密码建立一个连接,另一个允许匿名登录,但我无法想象为什么。但是,这样做必须逐个连接指定。网络上有一个不正确的示例,其中包括

remote.connection.conn2.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false

和连接的用户名/密码属性。这样做的最终结果是凭据可能不正确,您仍然可以登录。我试过这个,发现确实如此。

6) 尽管有表象,但指定 的java.naming.provider.url是必要的。如果 JBoss 可以从上面提到的连接主机和端口属性中解决这个问题,那就太好了,但它不能!这可能有也可能没有充分的理由,我根本不知道。

令人讨厌的是,这不能在属性文件中指定。 这似乎是 JBoss 客户端中的一个错误。 由于“:”在 Java 属性文件规范中等同于“=”,因此不可能将带有http-remote://表示法的 URL 或带有冒号斜杠的任何 url 存储在那里。冒号必须用反斜杠转义,但显然 JBoss 客户端代码没有调用 Properties.load() 来正确解决转义问题,而是尝试逐行读取它?所以这个必须在Properties中指定,传递给InitialContext创建。我已经尝试了这两种方法,发现在代码中指定它是有效的,而在属性文件中指定它却不行。

所以我们遇到了不幸的情况,有两种方法向 InitialContext 提供数据,一些通过属性文件,一些在初始环境 Hashtable 中传递给 InitialContext 构造函数。有些事情必须在一个地方做,有些事情必须在另一个地方做。而且这些都没有正确记录。

于 2017-01-13T17:13:57.427 回答