Jboss EAP 6 EJB调用常见问题
1. 调用EJB的三种方法
调用EAP 6 EJB的第一种方法,使用JBoss API,如下:
Properties p = new Properties(); p.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false"); p.put("remote.connections", "default"); p.put("remote.connection.default.host", "localhost"); p.put("remote.connection.default.port", "4447"); p.put("remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS", "false"); EJBClientConfiguration cc = new PropertiesBasedEJBClientConfiguration(p); ContextSelector<EJBClientContext> selector = new ConfigBasedEJBClientContextSelector(cc); EJBClientContext.setSelector(selector); Properties props = new Properties(); props.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); InitialContext context = new InitialContext(props); final boolean useScopedExample = Boolean.getBoolean("UseScopedContext"); final String rcal = "ejb:jboss-ejb-multi-server-app-main/ejb//" + (useScopedExample ? "MainAppSContextBean" : "MainAppBean") + "!" + MainApp.class.getName(); final MainApp remote = (MainApp) context.lookup(rcal);
这种方法在Standalone client (client is not running inside of JBoss EAP 6)能正常调用,但在EAP 6环境中会报java.lang.SecurityException: EJBCLIENT000021: EJB client context selector may not be changed。
第二种方法使用scoped context,代码如下:
Properties props = new Properties(); props.put("org.jboss.ejb.client.scoped.context", "true"); props.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); //props.put(Context.OBJECT_FACTORIES, "org.jboss.ejb.client.naming.ejb.ejbURLContextFactory"); props.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false"); props.put("remote.connections", "default"); props.put("remote.connection.default.port", 4447); props.put("remote.connection.default.host", host); //props.put("remote.connection.default.username", username); //props.put("remote.connection.default.password", password); props.put("remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOPLAINTEXT", "false"); props.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_POLICY_NOPLAINTEXT", "false"); Context context = (Context) new InitialContext(props).lookup("ejb:"); try { final TimerExample bean = (TimerExample) context.lookup("TestTimer/TestTimerEJB/TimerExampleBean!org.example.jboss.timer.TimerExample"); bean.doSomething(); } finally { try { context.close(); } catch(Exception e) { } }
这种方式,可以完全通过代码实现配置,但由客户端来创建和管理Connection,因此需要更多的资源,性能不高。还有一个严重的问题,当使用CMT,事物跨多个EJB时,因每次调用EJB后都关闭context(如不close,connection会一直保持),这将造成事务commit/rollback失败。这种情况下,就得使用第三种方式,也是推荐的方式:
先来看代码:
Properties props = new Properties(); props.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); final Context context = new javax.naming.InitialContext(props); final Greeter bean = (Greeter) context.lookup("ejb:myapp/myejb/GreeterBean!" + org.myapp.ejb.Greeter.class.getName()); bean.doSometing();
代码很简单,注意没有调用close()方法。那这是怎么找到远程主机的呢?需要增加一些配置,先在war的WEB-INF或ear的META-INF中新建一个文件jboss-ejb-client.xml,内容如下:
<jboss-ejb-client xmlns="urn:jboss:ejb-client:1.2"> <client-context> <ejb-receivers> <remoting-ejb-receiver outbound-connection-ref="remote-ejb-connection-app1" connect-timeout="10000"/> <remoting-ejb-receiver outbound-connection-ref="remote-ejb-connection-app2" connect-timeout="10000"/> </ejb-receivers> </client-context> </jboss-ejb-client>
然后在standalone.xml增加如下配置,建立与远程主机的关联:
<subsystem xmlns="urn:jboss:domain:remoting:1.2"> <connector name="remoting-connector" socket-binding="remoting" security-realm="ApplicationRealm"/> <outbound-connections> <remote-outbound-connection name="remote-ejb-connection-app1" outbound-socket-binding-ref="remote-ejb-app1"> <properties> <property name="SASL_POLICY_NOANONYMOUS" value="false"/> <property name="SSL_ENABLED" value="false"/> </properties> </remote-outbound-connection> <remote-outbound-connection name="remote-ejb-connection-app2" outbound-socket-binding-ref="remote-ejb-app2"> <properties> <property name="SASL_POLICY_NOANONYMOUS" value="false"/> <property name="SSL_ENABLED" value="false"/> </properties> </remote-outbound-connection> </outbound-connections> </subsystem> ... <socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}"> <outbound-socket-binding name="remote-ejb-app1"> <remote-destination host="localhost" port="4447"/> </outbound-socket-binding> <outbound-socket-binding name="remote-ejb-app2"> <remote-destination host="localhost" port="4447"/> </outbound-socket-binding> </socket-binding-group>
调用EJB后,来查看一下connection情况:netstat -aon | findstr "4447"(netstat -np | grep 4447)
2. Could not register a EJB receiver: java.lang.RuntimeException: Operation failed with status WAITING
EJB调用配置中包含以下三个timeout参数:
- invocation.timeout
The timeout for the EJB handshake or method invocation request/response cycle. The value is in milliseconds.
The invocation of any method throws a java.util.concurrent.TimeoutException if the execution takes longer than the timeout period. The execution completes and the server is not interrupted.
- reconnect.tasks.timeout
The timeout for the background reconnect tasks. The value is in milliseconds.
If a number of connections are down, the next client EJB invocation will use an algorithm to decide if a reconnect is necessary to find the right node.
- remote.connection.CONNECTION_NAME.connect.timeout
The timeout period for the initial connection. After that, the reconnect task will periodically check whether the connection can be established. The value is in milliseconds.
remote.connection.CONNECTION_NAME.connect.timeout默认值为5000ms(参见代码InitialContextFactory.getInitialContext),当在timeout时间内连接不上时,就会报以上错误。
3. 关于Rollback
EJB方法抛出RuntimeException会引起rollback,那有方法指定某个Exception是否引起Rollback么?可以使用注解@ApplicationException,如下:
@ApplicationException(rollback = true)
public class MyException extends Exception {
// ...
}
4. How to close scoped EJB client contexts?
Internally, when a lookup happens for a ejb: URL string, a relevant javax.naming.Context is created for that ejb: lookup.
So we first create a JNDI context and then use it to lookup an EJB.
final Properties props = new Properties(); // mark it for scoped EJB client context props.put("org.jboss.ejb.client.scoped.context","true"); // add other properties props.put(....); ... Context jndiCtx = new InitialContext(props); Context ejbRootNamingContext = (Context) jndiCtx.lookup("ejb:"); try { final MyBean bean = ejbRootNamingContext.lookup("app/module/distinct/bean!interface"); // rest of the EJB jndi lookup string bean.doSomething(); } finally { try { // close the EJB naming JNDI context ejbRootNamingContext.close(); } catch (Throwable t) { // log and ignore } try { // also close our other JNDI context since we are done with it too jndiCtx.close(); } catch (Throwable t) { // log and ignore } }