【EJB】Developing EJB Applications -- Chapter5(调用会话Bean)

     调用会话Bean

5.1 使用JNDI远程调用一个会话Bean

此任务描述如何使用JNDI为远程客户端添加对调用会话bean的支持。 该任务假定项目正在使用Maven构建。
        ejb远程快速启动包含演示此功能的Maven项目。 快速启动包含要部署的会话bean和远程客户端的项目。 下面的代码示例取自远程客户端项目。
        此任务假定会话bean不需要身份验证。


        警告
        Red Hat建议在所有受影响的程序包中明确禁用SSLv2,SSLv3和TLSv1.0,以支持TLSv1.1或TLSv1.2。

       先决条件:
      1.您必须已经准备好可以使用的Maven项目。
       2.已经添加了JBoss EAP Maven存储库的配置。
       3.您要调用的会话bean已经部署。
       4.部署的会话bean实现远程业务接口。
       5.会话bean的远程业务接口可用作Maven依赖关系。如果远程业务接口仅作为JAR文件使用,那么建议将JAR作为工件添加到Maven存储库中。有关安装,请参阅Maven文档:install-file路线的目标,http://maven.apache.org/plugins/maven-install-plugin/usage.html
       6.您需要知道托管会话bean的服务器的主机名和JNDI端口。


      要从远程客户端调用会话bean,您必须首先正确配置项目。


      添加Maven项目配置以远程调用会话Bean:
      1.添加所需的项目依赖关系。
         必须更新项目的pom.xml以包含必要的依赖关系。
      2.添加jboss-ejb-client.properties文件。


       JBoss EJB客户端API希望在名为jboss-ejb-client.properties的项目的根目录中找到一个包含JNDI服务的连接信息的文件。将此文件添加到项目的src / main / resources /目录中,并具有以下内容。


       EJB客户端属性文件示例
remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false
remote.connections=default
remote.connection.default.host=localhost
remote.connection.default.port = 8080
remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false
#remote.connection.default.connect.options.org.xnio.Options.SSL_STARTTLS=true
        更改主机名和端口以匹配您的服务器。 默认端口号为8080.对于安全连接,请将SSL_ENABLED行设置为true并取消注释SSL_STARTTLS行。 容器中的Remoting界面支持使用相同端口的安全和不安全的连接。


       3.添加对远程业务接口的依赖关系。
       将会话bean的远程业务接口上的Maven依赖关系添加到pom.xml。


        POM文件配置示例
<dependency>
    <groupId>org.jboss.quickstarts.eap</groupId>
    <artifactId>jboss-ejb-remote-server-side</artifactId>
    <type>ejb-client</type>
    <version>${project.version}</version>
</dependency>

       项目配置正确后,您可以添加代码来访问和调用会话bean。

        使用JNDI并调用Bean的方法获取Bean代理

        1.处理检查的异常。
         以下代码中使用的两种方法(InitialContext()和lookup())具有类型为javax.naming.NamingException的检查异常。 这些方法调用必须包含在捕获NamingException或声明为抛出NamingException的方法的try / catch块中。 ejb远程快速启动使用第二种技术。


       2.创建一个JNDI上下文。
       JNDI Context对象提供从服务器请求资源的机制。 使用以下代码创建一个JNDI上下文:


       创建一个JNDI上下文代码示例:
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-ejb:add(host=localhost, port=8080)

      从jboss-ejb-client.properties文件读取JNDI服务的连接属性。


      3.使用JNDI Context的lookup()方法来获取一个bean代理。


      调用bean代理的lookup()方法,并传递所需的会话bean的JNDI名称。 这将返回一个必须转换为包含要调用方法的远程业务接口类型的对象。


       调用查找方法代码示例:

final RemoteCalculator statelessRemoteCalculator = (RemoteCalculator) context.lookup(
"ejb:/jboss-ejb-remote-server-side//CalculatorBean!" +
RemoteCalculator.class.getName());

       会话bean JNDI名称使用特殊语法定义。 有关更多信息,请参阅EJB JNDI命名参考。

       4.调用方法:现在您有一个代理bean对象,您可以调用远程业务界面中包含的任何方法。
       调用远程方法代码示例:

int a = 204;
int b = 340;
System.out.println("Adding " + a + " and " + b + " via the remote stateless calculator deployed on the server");
int sum = statelessRemoteCalculator.add(a, b);
System.out.println("Remote calculator returned sum = " + sum);
        代理bean将方法调用请求传递到服务器上执行的会话bean。结果返回给代理bean,然后将其返回给调用者。代理bean和远程会话bean之间的通信对调用者来说是透明的。
        您现在应该可以配置一个Maven项目来支持在远程服务器上调用会话bean,并使用JNDI从服务器检索的代理bean编写代码来调用会话bean的方法。


5.2 关于EJB客户端上下文

       JBoss EAP引入了用于管理远程EJB调用的EJB客户机API。 JBoss EJB客户端API使用EJBClientContext,它可以与一个或多个线程同时关联并由其使用。这意味着EJBClientContext可以包含任意数量的EJB接收器。 EJB接收器是知道如何与能够处理EJB调用的服务器通信的组件。通常,EJB远程应用程序可以分为以下几种:
        *作为独立Java应用程序运行的远程客户端。
        *在另一个JBoss EAP实例中运行的远程客户端。
        根据远程客户端的类型,从EJB客户端API的角度来看,JVM中可能有多个EJBClientContext。
        虽然独立应用程序通常具有可以由任意数量的EJB接收器支持的单个EJBClientContext,但这并不是强制性的。如果独立应用程序具有多个EJBClientContext,则EJB客户机上下文选择器负责返回适当的上下文。
        如果在另一个JBoss EAP实例中运行的远程客户端,每个部署的应用程序将具有相应的EJB客户机上下文。无论何时该应用程序调用另一个EJB,相应的EJB客户机上下文将用于查找正确的EJB接收器,然后处理该调用。


5.3 使用单个EJB语境时的考虑

概要:
        在单独的远程客户端使用单个EJB客户机上下文时,必须考虑应用程序的需求。 有关不同类型的远程客户机的更多信息,请参阅:关于EJB客户机上下文。

        具有单个EJB客户端上下文的远程独立客户机的典型过程:
        远程独立客户机通常只有一个EJB客户机上下文由任何数量的EJB接收器支持。 以下是独立远程客户端应用程序的示例:

public class MyApplication {
    public static void main(String args[]) {
        final javax.naming.Context ctxOne = new javax.naming.InitialContext();
        final MyBeanInterface beanOne = ctxOne.lookup("ejb:app/module/distinct/bean!interface");
        beanOne.doSomething();
    }
}
        远程客户端JNDI查找通常由jboss-ejb-client.properties文件支持,该文件用于设置EJB客户机上下文和EJB接收器。此配置还包括安全凭据,然后用于创建连接到JBoss EAP服务器的EJB接收器。当调用上述代码时,EJB客户端API会查找EJB客户机上下文,然后用于选择将接收和处理EJB调用请求的EJB接收器。在这种情况下,只有单个EJB客户端上下文,因此上述代码用于调用该上下文。使用JNDI远程调用会话bean的过程将在此更详细地描述:使用JNDI远程调用会话Bean。

        远程独立客户端需要不同的凭据
        用户应用程序可能想要多次调用bean,但是可以使用不同的安全凭证连接到JBoss EAP服务器。以下是一个独立的远程客户端应用程序的例子,它们调用同一个bean两次:

public class MyApplication {
    public static void main(String args[]) {
        // Use the "foo" security credential connect to the server and invoke this bean instance
        final javax.naming.Context ctxOne = new javax.naming.InitialContext();
        final MyBeanInterface beanOne = ctxOne.lookup("ejb:app/module/distinct/bean!interface");
        beanOne.doSomething();
        ...
        // Use the "bar" security credential to connect to the server and invoke this bean instance
        final javax.naming.Context ctxTwo = new javax.naming.InitialContext();
        final MyBeanInterface beanTwo = ctxTwo.lookup("ejb:app/module/distinct/bean!interface");
        beanTwo.doSomething();
        ...
    }
}

        在这种情况下,应用程序想要连接到同一个服务器实例来调用该服务器上托管的EJB,但是想要在连接到服务器时使用两种不同的凭据。 因为客户机应用程序具有单个EJB客户机上下文,每个服务器实例只能有一个EJB接收器,这意味着上述代码只使用一个凭据来连接到服务器,并且代码不按应用程序期望执行。

 

解决方案

         范围的EJB客户端上下文为此问题提供了解决方案。它们提供了一种对EJB客户端上下文及其相关联的JNDI上下文(通常用于EJB调用)的更多控制的方法。 有关范围限定的EJB客户机上下文的更多信息,请参阅使用Scoped EJB客户端上下文并使用Scoped EJB客户端上下文配置EJB。

 

5.4 EJB调用的事务行为

        服务器到服务器调用

        必须处理分布式JBoss EAP应用程序的事务属性,以便在同一服务器上调用应用程序。要终止事务,目标方法必须使用不同的接口标记为REQUIRES_NEW。

 

         可以使用以下任一方法调用EJB:

         1.EJB远程呼叫

         2.互联网ORB协议(IIOP)远程呼叫

注意:

           如果两个服务器都是JBoss EAP,则JBoss EAP不需要Java事务服务(JTS)来进行服务器到服务器EJB调用的事务传播。 JBoss EJB客户端API库自身处理它。

 

         EJB远程呼叫

         要使用JBoss EAP独立客户端调用EJB会话bean,客户端必须对使用EJB代理或UserTransaction的InitialContext对象引用。在使用EJB代理或UserTransaction时,保持InitialContext对象也是很重要的。连接的控制将在由InitialContext创建的类与属性的内部。

         以下代码示例显示了一个持有对InitialContext对象的引用的EJB客户端。该代码示例来自JBoss EAP附带的ejb-multi-server quickstart。

         EJB客户端代码示例
package org.jboss.as.quickstarts.ejb.multi.server;

import java.util.Date;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.naming.Context;
import javax.naming.InitialContext;

import org.jboss.as.quickstarts.ejb.multi.server.app.MainApp;
import org.jboss.ejb.client.ContextSelector;
import org.jboss.ejb.client.EJBClientConfiguration;
import org.jboss.ejb.client.EJBClientContext;
import org.jboss.ejb.client.PropertiesBasedEJBClientConfiguration;
import org.jboss.ejb.client.remoting.ConfigBasedEJBClientContextSelector;

public class Client {

/**
* @param args no args needed
* @throws Exception
*/
    public static void main(String[] args) throws Exception {
        // suppress output of client messages
        Logger.getLogger("org.jboss").setLevel(Level.OFF);
        Logger.getLogger("org.xnio").setLevel(Level.OFF);

        Properties p = new Properties();
        p.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false");
        p.put("remote.connections", "one");
        p.put("remote.connection.one.port", "8080");
        p.put("remote.connection.one.host", "localhost");
        p.put("remote.connection.one.username", "quickuser");
        p.put("remote.connection.one.password", "quick-123");

        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 String rcal = "ejb:jboss-ejb-multi-server-app-main/ejb//" + ("MainAppBean") + "!" + MainApp.class.getName();
        final MainApp remote = (MainApp) context.lookup(rcal);
        final String result = remote.invokeAll("Client call at "+new Date());

        System.out.println("InvokeAll succeed: "+result);
    }

}

注意

        对于具有范围的EJB客户端上下文以及使用远程命名协议的调用的方案,不支持在客户机上获取UserTransaction引用。 这是因为在这些情况下,InitialContext封装了自己的EJB客户端上下文实例; 这不能使用EJBClient类的静态方法访问。当EJBClient.getUserTransaction()被调用时,它从默认(全局)EJB客户端上下文(可能未被初始化)而不是从所需的EJB客户端上下文返回一个事务。

 

        UserTransaction参考在客户端

        以下示例显示如何在独立客户端上获取UserTransaction引用。


       UserTransaction代码示例
import org.jboss.ejb.client.EJBClient;
import javax.transaction.UserTransaction;
...
    Context context = null;
    UserTransaction tx = null;
    try {
      Properties props = new Properties();
      // REMEMBER: there must be a jboss-ejb-client.properties with the
      // connection parameter in the client's classpath
      props.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
      context = new InitialContext(props);
      System.out.println("\n\tGot initial Context: "+context);
      tx = EJBClient.getUserTransaction("yourServerName");
      System.out.println("UserTransaction = "+tx.getStatus());
      tx.begin();
      // do some work
      ...
    } catch (Exception e) {
      e.printStackTrace();
      tx.rollback();
    } finally{
      if(context != null) {
        context.close();
      }
    }

注意

       在客户端获取UserTransaction参考; 使用以下系统属性-Djboss.node.name = yourServerName启动服务器,然后在客户端使用它,如下所示:
tx = EJBClient.getUserTransaction("yourServerName");

       将“yourServerName”替换为服务器的名称。 如果在节点上启动用户事务,那么节点上的所有调用都是粘性的,并且节点必须具有所有需要的EJB。 不可能使用UserTransaction与远程命名协议和作用域上下文。

 

        互联网ORB协议(IIOP)远程呼叫

        要使用IIOP远程调用调用EJB bean,必须首先在服务器上启用IIOP。

        要启用IIOP,您必须安装iiop-openjdk子系统,并在ejb3子系统配置中显示<iiop />元素。发行版附带的standalone-full.xml配置都启用了这两个配置。

 

        对于通过IIOP远程调用可以访问的bean,需要使用EJB 2和家庭接口缩小。有关IIOP远程调用的更多详细信息,请参阅配置IIOP远程EJB调用。

 

        注意

        IIOP远程调用和EJB远程调用之间的主要区别是:

        当客户端打算通过IIOP远程调用调用EJB bean以在客户端启动事务时,必须使用JTS事务实现。另一方面,如果客户端打算通过EJB远程调用调用EJB bean,则必须使用JTA事务实现。

        对于通过IIOP远程调用的EJB调用,事务在客户机上创建并通过调用传播到服务器。而通过EJB远程调用进行EJB调用,事务在服务器上查找并在客户端进行管理。

        要在服务器上启用JTS事务,必须在iiop-openjdk子系统中将事务属性从值spec更改为完整,并将事务子系统中的jts属性设置为true。您可以使用以下管理CLI命令来实现此目的。
/subsystem=iiop-openjdk/:write-attribute(name=transactions,value=full)
/subsystem=transactions/:write-attribute(name=jts,value=true)


        重要
        对于客户端使用IIOP调用成功调用EJB事务,我们需要在org.wildfly中添加客户端依赖关系:wildfly-iiop-openjdk。

IIOP客户端代码实例:

import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import com.arjuna.ats.arjuna.recovery.RecoveryManager;
import com.arjuna.ats.internal.jts.ORBManager;
import com.arjuna.ats.internal.jts.context.ContextPropagationManager;
import com.arjuna.ats.jts.OTSManager;
import com.sun.corba.se.impl.orbutil.ORBConstants;
import com.arjuna.orbportability.ORB;
import com.arjuna.orbportability.OA;

final String host = "localhost";
final int port = 3528;

// For client we define how the Narayana will behave
System.setProperty("com.arjuna.ats.jts.alwaysPropagateContext", "true");

// Set orb to be initialized on client and being able to start ORB txn
Properties properties = new Properties();
properties.setProperty(ORBConstants.PERSISTENT_SERVER_PORT_PROPERTY, "15151");
properties.setProperty(ORBConstants.ORB_SERVER_ID_PROPERTY, "1");

// registers the appropriate filter with the ORB
new ContextPropagationManager();

org.omg.CORBA.ORB sunOrb = org.omg.CORBA.ORB.init(new String[0], properties);
ORB orb = null;
try {
    orb = com.arjuna.orbportability.ORB.getInstance("ClientSide");
    orb.setOrb(sunOrb);

    OA oa = OA.getRootOA(orb);
    org.omg.PortableServer.POA rootPOA = org.omg.PortableServer.POAHelper.narrow(sunOrb.resolve_initial_references("RootPOA"));
    oa.setPOA(rootPOA);

    oa.initOA();

    ORBManager.setORB(orb);
    ORBManager.setPOA(oa);

    // Recovery manager has to be started on client when we want recovery to work at client
    RecoveryManager.manager().startRecoveryManagerThread();

    // Getting context to lookup
    System.setProperty("com.sun.CORBA.ORBUseDynamicStub", "true");
    final Properties prope = new Properties();
    prope.put(Context.PROVIDER_URL, "corbaloc::" + host + ":" + port + "/JBoss/Naming/root");
    prope.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.iiop.naming:org.jboss.naming.client");
    prope.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.cosnaming.CNCtxFactory");
    Context context = new InitialContext(prope);

    // Bean lookup
    final Object iiopObj = context.lookup(IIOPBeanMandatory.class.getSimpleName());
    final IIOPBeanHome beanHome = (IIOPBeanHome) PortableRemoteObject.narrow(iiopObj, IIOPBeanHome.class);
    final IIOPRemote bean = beanHome.create();

    // Starting orb transaction
    OTSManager.get_current().begin();

    // Call bean - business logic
    bean.sayHello();

    // Manage the commit of the work
    OTSManager.get_current().commit(true);
    // or rollback
    // OTSManager.get_current().rollback();
} finally {
    // It's good to release resources - do it only once at the end
    if (orb != null) {
        orb.shutdown();
    }
    RecoveryManager.manager().terminate();
}
       有关详细信息,请参阅“JBoss EAP配置指南”中的配置事务。

5.5 来自远程服务器实例的示例EJB发起

        默认情况下,JBoss EAP是安全的。 远程客户机的服务器实例不会发生通信,而不需要传递适当的凭据,无论它是独立客户端还是其他服务器实例。 为了允许客户端服务器与目标服务器通信,您必须配置在此服务器通信期间使用的用户凭据。


        以下示例演示如何从另一个远程JBoss EAP服务器实例调用部署在JBoss EAP服务器实例上的EJB。 为了便于参考,我们使用以下别名:
        *客户端服务器:EJB调用发生的服务器。
        *目标服务器:部署EJB的服务器。

       先决条件:
       *在目标服务器上配置具有所需凭据的用户。 有关详细信息,请参阅“JBoss EAP配置指南”中的添加管理用户。
       *启动目标服务器。

./standalone.sh -server-config=standalone-full.xml

        部署应用程序 有关详细信息,请参阅“JBoss EAP配置指南”中的部署应用程序。

注意

        每个服务器实例必须具有唯一的jboss.node.name系统属性。 您可以通过将其传递给启动脚本来设置此值:

./standalone.sh -server-config=standalone-full.xml -Djboss.node.name=<add appropriate value here>

 

       5.5.1 配置客户端服务器

       您必须让客户端服务器知道目标服务器的EJB远程连接器,并在EJB调用期间进行通信。 要实现此目的,您必须在客户端服务器上的远程处理子系统中添加一个远程出站连接。远程出站连接配置指示将从此客户端服务器向远程服务器实例创建出站连接。 远程出站连接必须具有自己配置的出站套接字绑定,指向远程主机和目标服务器的远程端口。

       1、启动客户端服务器:

/standalone.sh -server-config=standalone-full.xml -Djboss.socket.binding.port-offset=100
        2、在客户端服务器上创建一个安全领域,以与安全的目标服务器进行通信。 客户端服务器必须向目标服务器提供用户凭据。 要实现此目的,您需要在客户端服务器上创建安全领域,这将传递为添加到目标服务器的用户提供的用户信息。
        您必须使用存储base64编码密码的安全领域,然后在被要求时传递这些凭据。 您需要创建为最初为目标服务器创建的用户提供的密码的base64编码版本。 您可以使用OpenSSL在命令行中生成base64编码的密码。

echo -n “password” | openssl dgst -sha256 -binary | openssl base64

        这里密码为纯文本 - 密码 - 被插入到OpenSSL摘要函数中,然后管线到另一个OpenSSL函数中,以转换为base64编码格式。 您现在可以在客户端服务器上配置的安全领域中使用base64编码的密码。


        3、运行以下管理CLI命令以创建base64编码密码的安全域:

/core-service=management/security-realm=ejb-security-realm:add()
/core-service=management/security-realm=ejb-security-realm/server-identity=secret:add(value=<base64-encoded password>)
        您可能会注意到,管理CLI显示消息“process-state”⇒“reload-required”,因此您必须重新启动服务器才能使用此更改。

        成功调用此命令后,将在standalone.xml的<management>部分中创建以下配置:
<management>
        <security-realms>
            ...
            <security-realm name="ejb-security-realm">
                <server-identities>
                    <secret value=<base64-encoded password>/>
                </server-identities>
            </security-realm>
        </security-realms>
...
       上面的代码片段使用base64编码的密码创建了一个名为ejb-security-realm的安全领域。


        4.在客户端服务器上创建出站套接字绑定。 您现在必须创建一个指向目标服务器的主机和端口的出站套接字绑定。

/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-ejb:add(host=localhost, port=8080)

       上面的命令创建一个名为remote-ejb的出站套接字绑定,它指向localhost作为主机,端口8080作为目标端口。 请注意,主机信息应与目标服务器的主机/ IP相匹配。 在这个例子中,我们在同一台机器上运行客户机和目标服务器,所以我们使用localhost。 类似地,端口信息应该与ejb3子系统使用的http-remoting连接器端口匹配; 默认为8080。

       当此命令成功运行时,您将看到在suite-binding-group中使用以下outbound-socket-binding更新了standalone-full.xml:

<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
       ...
       <outbound-socket-binding name="remote-ejb">
          <remote-destination host="localhost" port="8080"/>
       </outbound-socket-binding>
   </socket-binding-group>

         5.创建使用此新创建的出站套接字绑定的远程出站连接。现在让我们创建一个远程出站连接,它将使用新创建的outbound-socket-binding指向目标服务器的EJB remoting连接器:

/subsystem=remoting/remote-outbound-connection=remote-ejb-connection:add(outbound-socket-binding-ref=remote-ejb, protocol=http-remoting, security-realm=ejb-security-realm, username=ejb)

       上述命令在远程处理子系统中创建一个名为remote-ejb-connection的远程出站连接,并使用以前创建的remote-ejboutbound-socket-binding。请注意上述命令中的outbound-socket-binding-ref,使用http-remoting协议。此外,我们还将security-realm属性设置为我们在上一步中创建的安全领域。还要注意,用户名属性设置为使用允许与目标服务器进行通信的用户。

        此步骤将在客户端服务器上创建到远程目标服务器的出站连接,并将用户名设置为允许与该目标服务器进行通信的用户。它还将安全领域设置为能够传递用户凭据(在这种情况下为密码)的预配置安全领域。这样当必须从客户端服务器建立连接到目标服务器时,连接创建逻辑将具有必要的安全凭证来传递并建立成功的安全连接。

       让我们运行以下两个操作来设置出站连接的一些默认连接创建选项:

/subsystem=remoting/remote-outbound-connection=remote-ejb-connection/property=SASL_POLICY_NOANONYMOUS:add(value=false)
/subsystem=remoting/remote-outbound-connection=remote-ejb-connection/property=SSL_ENABLED:add(value=false)

       最终,在成功调用此命令后,将在远程处理子系统中创建以下配置:

<subsystem xmlns="urn:jboss:domain:remoting:1.1">
...
   <outbound-connections>
        <remote-outbound-connection name="remote-ejb-connection" outbound-socket-binding-ref="remote-ejb" protocol="http-remoting" security-realm="ejb-security-realm" username="ejb">
            <properties>
                <property name="SASL_POLICY_NOANONYMOUS" value="false"/>
                <property name="SSL_ENABLED" value="false"/>
            </properties>
        </remote-outbound-connection>
    </outbound-connections>
</subsystem>

      这完成了我们在客户端服务器上的配置。 我们的下一步是在客户端服务器上部署应用程序,该服务器将调用目标服务器上部署的bean。

 

       5.5.2将jboss-ejb-client.xml添加到客户端应用程序

       将jboss-ejb-client.xml添加到客户端应用程序作为META-INF / jboss-ejb-client.xml:

<jboss-ejb-client xmlns="urn:jboss:ejb-client:1.0">
   <client-context>
       <ejb-receivers>
           <remoting-ejb-receiver outbound-connection-ref="remote-ejb-connection"/>
       </ejb-receivers>
   </client-context>
</jboss-ejb-client>

       请注意,我们已将此应用程序的EJB客户机上下文配置为使用指向远程出站连接的远程远程接收器,该远程出站连接(我们之前创建的)指向remote-ejb-connection。这将链接EJB客户端上下文以使用指向目标服务器上的EJB remoting连接器的remote-ejb-connection。

 

        5.5.3 调用Bean

        以下片段显示如何调用该bean:

import javax.naming.Context;
import java.util.Hashtable;
import javax.naming.InitialContext;
…
public void invokeOnBean() {
        try {
            final Hashtable props = new Hashtable();
            // setup the ejb: namespace URL factory
            props.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
            // create the InitialContext
            final Context context = new javax.naming.InitialContext(props);
            // Lookup the Greeter bean using the ejb: namespace syntax which is explained here https://docs.jboss.org/author/display/AS71/EJB+invocations+from+a+remote+client+using+JNDI
            final Greeter bean = (Greeter) context.lookup("ejb:" + "myapp" + "/" + "myejb" + "/" + "" + "/" + "GreeterBean" + "!" + org.myapp.ejb.Greeter.class.getName());
            // invoke on the bean
            final String greeting = bean.greet("Tom");
            System.out.println("Received greeting: " + greeting);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
}

       上面的代码将调用目标服务器上部署的bean并返回结果。

 

         5.5.4部署客户端应用程序

        让我们在客户端服务器上部署客户端应用程序。您可以使用CLI或管理控制台或IDE或手动部署到EAP_HOME/ standalone / deployments文件夹。确保应用程序已部署成功。

        有关详细信息,请参阅“JBossEAP配置指南”中的部署应用程序。

 

5.6 EJB客户端上下文的使用范围

       概要

       调用EJB在早期版本的JBoss EAP中,通常会创建一个JNDI上下文并传递给指向目标服务器的PROVIDER_URL。对使用该JNDI上下文查找的EJB代理进行的任何调用都将终止于该服务器。使用范围的EJB客户端上下文,用户应用程序可以控制哪个EJB接收器用于特定的调用。

 

       在远程独立客户端中使用范围化的EJB客户端上下文

       在引入范围限定的EJB客户端上下文之前,上下文通常作用于客户端应用程序。范围客户端上下文现在允许使用JNDI上下文来限定EJB客户机上下文范围。以下是使用范围的EJB客户端上下文调用相同的bean两次的独立远程客户端应用程序的示例:

public class MyApplication {
    public static void main(String args[]) {
        // Use the "foo" security credential connect to the server and invoke this bean instance
        final Properties ejbClientContextPropsOne = getPropsForEJBClientContextOne():
        final javax.naming.Context ctxOne = new javax.naming.InitialContext(ejbClientContextPropsOne);
        final MyBeanInterface beanOne = ctxOne.lookup("ejb:app/module/distinct/bean!interface");
        beanOne.doSomething();
        ...
        ctxOne.close();

        // Use the "bar" security credential to connect to the server and invoke this bean instance
        final Properties ejbClientContextPropsTwo = getPropsForEJBClientContextTwo():
        final javax.naming.Context ctxTwo = new javax.naming.InitialContext(ejbClientContextPropsTwo);
        final MyBeanInterface beanTwo = ctxTwo.lookup("ejb:app/module/distinct/bean!interface");
        beanTwo.doSomething();
        ...
        ctxTwo.close();
    }
}

        要使用作用域的EJB客户端上下文,可以以编程方式配置EJB客户端属性,并在上下文创建时传递属性。属性与标准jboss-ejb-client.properties文件中使用的属性相同。要将EJB客户端上下文作用于JNDI上下文,您还必须指定org.jboss.ejb.client.scoped.context属性并将其值设置为true。该属性通知EJB客户机API,它必须创建一个由EJB接收器支持的EJB客户机上下文,然后创建的上下文仅对创建它的JNDI上下文进行作用域或可见。使用此JNDI上下文查找或调用的任何EJB代理将只知道与此JNDI上下文相关联的EJB客户机上下文。应用程序用于查找和调用EJB的其他JNDI上下文不会知道其他作用域的EJB客户端上下文。

        不通过org.jboss.ejb.client.scoped.context属性并且不限定为EJB客户端上下文的JNDI上下文将使用默认行为,即使用通常与该EJB客户机上下文相关联的现有EJB客户机上下文整个应用程序。

        范围EJB客户端上下文为用户应用程序提供了与以前版本的JBoss EAP中基于JNP的JNDI调用相关联的灵活性。它为用户应用程序提供了更多的控制权,JNDI上下文与哪个服务器通信,以及如何连接到该服务器。

 

         注意

         使用范围上下文,底层资源不再由容器或API处理,因此当不再需要InitialContext时,必须关闭它们。当InitialContext关闭时,资源将立即释放。与它绑定的代理不再有效,任何调用将抛出异常。未能关闭InitialContext可能会导致资源和性能问题。

 

       5.6.1。使用范围EJB客户端上下文配置EJB

       可以使用基于地图的作用域上下文配置EJB。这是通过使用jboss-ejb-client.properties中找到的标准属性以编程方式填充属性映射来实现的,为org.jboss.ejb.client.scoped.context属性指定true,并在InitialContext创建时传递属性。

 

      使用范围上下文的好处是允许您在不直接引用EJB或导入JBoss类的情况下配置访问。它还提供了一种在多线程环境中在运行时配置和负载平衡主机的方法。

 

       使用基于映射的作用域上下文配置EJB:

       1、设置属性:

       以编程方式配置EJB客户端属性,指定与标准jboss-ejb-client.properties文件中使用的相同的属性集。要启用范围上下文,您必须指定org.jboss.ejb.client.scoped.context属性并将其值设置为true。以下是以编程方式配置属性的示例。

// Configure  EJB Client properties for the InitialContext
Properties ejbClientContextProps = new Properties();
ejbClientContextProps.put("remote.connections","name1");
ejbClientContextProps.put("remote.connection.name1.host","localhost");
ejbClientContextProps.put("remote.connection.name1.port","8080");
// Property to enable scoped EJB client context which will be tied to the JNDI context
ejbClientContextProps.put("org.jboss.ejb.client.scoped.context", "true");

        2、传递上下文创建中的属性:

// Create the context using the configured properties
InitialContext ic = new InitialContext(ejbClientContextProps);
MySLSB bean = ic.lookup("ejb:myapp/ejb//MySLSBBean!" + MySLSB.class.getName());

        3.关闭范围限定的EJB客户端上下文:

        查找ejb:string的根JNDI上下文以获取EJB命名上下文。然后使用ejbRootNamingContext实例来查找其余的EJB JNDI名称来获取EJB代理。使用close()方法来关闭ejbRootNamingContext和EJB JNDI上下文。关闭ejbRootNamingContext确保与JNDI上下文相关联的范围限定的EJB客户端上下文也被关闭。有效地,这将关闭与该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 = (MyBean)ejbRootNamingContext.lookup("app/module/distinct/bean!interface");
} 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
    }
}

        由查找EJB代理生成的上下文由此作用域上下文绑定,并且仅使用相关的连接参数。这使得可以创建不同的上下文来访问客户端应用程序中的数据,或者使用不同的登录来独立访问服务器。

 

        在客户端中,作用域的InitialContext和作用域代理都传递给线程,允许每个线程使用给定的上下文。也可以将代理传递给可以同时使用的多个线程。

 

        范围上下文EJB代理在远程调用上被序列化,然后在服务器上反序列化。当它被反序列化时,范围上下文信息被删除,并且返回到其默认状态。如果在远程服务器上使用反序列化代理,因为它不再具有在创建时使用的作用域上下文,这可能会导致EJBCLIENT000025错误,或者可能通过使用EJB名称来调用不需要的目标。

 

5.7 EJB客户端属性

        概要

        下表列出了可以通过编程方式或jboss-ejb-client.properties文件中配置的属性。

 

        EJB客户端全局属性

        下表列出了在同一范围内对整个库有效的属性。

 

                                        表5.1 Global 属性

Property Name

Description

endpoint.name

客户终端的名称。如果未设置,则默认值为客户机端点。

这可能有助于区分不同的端点设置,因为线程名称包含此属性。

remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED

指定是否为所有连接启用SSL协议的布尔值。警告

Red Hat建议在所有受影响的程序包中明确禁用SSLv2SSLv3TLSv1.0,以支持TLSv1.1TLSv1.2

deployment.node.selector

org.jboss.ejb.client.DeploymentNodeSelector的完整限定名称。

这用于负载平衡EJB的调用。

invocation.timeout

EJB握手或方法调用请求/响应周期的超时。值以毫秒为单位。

如果执行时间超过超时时间,任何方法的调用将抛出java.util.concurrent.TimeoutException异常。执行完成,服务器不中断。

reconnect.tasks.timeout

背景重新连接任务的超时。值以毫秒为单位。

如果多个连接断开,则下一个客户端EJB调用将使用算法来确定是否需要重新连接来查找正确的节点。

org.jboss.ejb.client.scoped.context

指定是否启用作用域EJB客户机上下文的布尔值。默认值为false

如果设置为true,则EJB Client将使用与JNDI上下文相关联的作用域上下文。否则,EJB客户端上下文将使用JVM中的全局选择器来确定用于调用远程EJB和主机的属性。


                                                                 表5.2  连接属性

Property Name

Description

remote.connections

活动连接名称的逗号分隔列表。 每个连接都使用此名称进行配置。

remote.connection.CONNECTION_NAME.host

连接的主机名或IP。

remote.connection.CONNECTION_NAME.port

连接端口。 默认值为8080。

remote.connection.CONNECTION_NAME.username

用于验证连接安全性的用户名。

remote.connection.CONNECTION_NAME.password

用于验证用户的密码。

remote.connection.CONNECTION_NAME.connect.timeout

初始连接的超时时间。 之后,重新连接任务将定期检查连接是否可以建立。 值以毫秒为单位。

remote.connection.CONNECTION_NAME.callback.handler.class

完全限定名称的CallbackHandler类。 它将用于建立连接,只要连接打开,就不能更改。

remote.connection.CONNECTION_NAME.channel.options.org.jboss.remoting3.RemotingOptions.MAX_OUTBOUND_MESSAGES

指定最大出站请求数的整数值。 默认值为80。

从客户机(JVM)到服务器只有一个连接来处理所有的调用。

remote.connection.CONNECTION_NAME.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS

确定凭据是否必须由客户端提供以连接成功的布尔值。 默认值为true。

如果设置为true,客户端必须提供凭据。 如果设置为false,只要远程连接器不请求安全域,就允许调用。

remote.connection.CONNECTION_NAME.connect.options.org.xnio.Options.SASL_DISALLOWED_MECHANISMS

禁用在连接创建期间用于认证的某些SASL机制。

JBOSS-LOCAL-USER表示当客户机和服务器位于同一台机器时使用的静默认证机制被禁用。

remote.connection.CONNECTION_NAME.connect.options.org.xnio.Options.SASL_POLICY_NOPLAINTEXT

在验证期间启用或禁用纯文本消息的布尔值。 如果使用JAAS,则必须将其设置为false以允许纯文本密码。

remote.connection.CONNECTION_NAME.connect.options.org.xnio.Options.SSL_ENABLED

指定是否为此连接启用SSL协议的布尔值。

警告

Red Hat建议在所有受影响的程序包中明确禁用SSLv2,SSLv3和TLSv1.0,以支持TLSv1.1或TLSv1.2。

remote.connection.CONNECTION_NAME.connect.options.org.jboss.remoting3.RemotingOptions.HEARTBEAT_INTERVAL

间隔在客户端和服务器之间发送心跳以防止自动关闭,例如,在防火墙的情况下。 值以毫秒为单位。


                                             表5.3。 群集属性

Property Name

Description

remote.cluster.CLUSTER_NAME.clusternode.selector

org.jboss.ejb.client.ClusterNodeSelector的完整限定名称。

此类,而不是org.jboss.ejb.client.DeploymentNodeSelector用于在集群环境中负载平衡EJB调用。 如果集群完全关闭,则调用将失败,并显示消息No ejb receiver可用。

remote.cluster.CLUSTER_NAME.channel.options.org.jboss.remoting3.RemotingOptions.MAX_OUTBOUND_MESSAGES

指定可以对整个集群进行的最大出站请求数的整数值。

remote.cluster.CLUSTER_NAME.node.NODE_NAME. channel.options.org.jboss.remoting3.RemotingOptions.MAX_OUTBOUND_MESSAGES

指定可以对此特定集群节点进行的最大出站请求数的整数值。


5.8 远程EJB数据压缩

        以前版本的JBoss EAP包括一个功能,其中包含EJB协议消息的消息流可以被压缩。 此功能已包含在JBoss EAP 6.3及更高版本中。

        注意

        压缩目前只能通过应该在客户端和服务器端的EJB接口上的注释来指定。 当前没有等效的XML来指定压缩提示。

 

        数据压缩提示可以通过JBoss注释org.jboss.ejb.client.annotation.CompressionHint来指定。 提示值指定是否压缩请求,响应或请求和响应。添加@CompressionHint默认为compressResponse= true和compressRequest = true。

 

        可以在接口级别指定注释以应用于EJB接口中的所有方法,如:

import org.jboss.ejb.client.annotation.CompressionHint;
@CompressionHint(compressResponse = false)
public interface ClassLevelRequestCompressionRemoteView {
    String echo(String msg);
}

       或者注释可以应用于EJB接口中的特定方法,如:

import org.jboss.ejb.client.annotation.CompressionHint;

public interface CompressableDataRemoteView {
    @CompressionHint(compressResponse = false, compressionLevel = Deflater.BEST_COMPRESSION)
    String echoWithRequestCompress(String msg);
    @CompressionHint(compressRequest = false)
    String echoWithResponseCompress(String msg);
    @CompressionHint
    String echoWithRequestAndResponseCompress(String msg);
    String echoWithNoCompress(String msg);
}

      上面显示的compressionLevel设置可以具有以下值:

      *BEST_COMPRESSION

      *BEST_SPEED

      *DEFAULT_COMPRESSION

      *NO_COMPRESSION

      compressionLevel设置默认为Deflater.DEFAULT_COMPRESSION。

 

       使用方法级覆盖的类级别注释:

@CompressionHint
public interface MethodOverrideDataCompressionRemoteView {

    @CompressionHint(compressRequest = false)
    String echoWithResponseCompress(final String msg);

    @CompressionHint(compressResponse = false)
    String echoWithRequestCompress(final String msg);

    String echoWithNoExplicitDataCompressionHintOnMethod(String msg);
}

       在客户端确保将org.jboss.ejb.client.view.annotation.scan.enabled系统属性设置为true。 该属性告知JBoss EJB Client扫描注释。

 

5.9 EJB客户端删除互操作性

       默认远程连接端口为8080. jboss-ejb-client属性文件如下所示:

remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false
remote.connections=default
remote.connection.default.host=localhost
remote.connection.default.port=8080
remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false

        默认连接器

        默认连接器是http-remoting。

        *如果客户端应用程序使用来自JBoss EAP 6的EJB客户端库,并且想要连接到JBoss EAP 7服务器,则服务器必须配置为在8080以外的端口上公开远程连接器。然后客户端必须使用新配置的连接器。

        *使用JBoss EAP 7的EJB客户端库并想要连接到JBoss EAP 6服务器的客户端应用程序必须知道服务器实例不使用http-remoting连接器,而是使用远程连接器。这是通过定义一个新的客户端连接属性来实现的。

remote.connection.default.protocol=remote

       注意

       仅JBoss EAP 6支持EJB远程调用。

       除了EJB客户端远程处理互操作性,您还可以使用以下选项连接到旧客户端:

       *在JBoss EAP配置指南中为JTS事务配置ORB。

 

 

 5.10 配置IIOP用于远程EJB呼叫

       JBoss EAP支持基于对JBoss EAP部署的EJB的基于CORBA/ IIOP的访问。

 

       <iiop>元素用于启用IIOP,CORBA,调用EJB。这个元素的存在意味着安装了iiop-openjdk子系统。 <iiop>元素包括以下两个属性:

        *enable-by-default:如果这是真的,那么所有EJB 2.x home接口的EJB都通过IIOP公开。否则,它们必须通过jboss-ejb3.xml显式启用。

        *use-qualified-name:如果这是true,那么EJB将绑定到CORBA命名上下文,其绑定名称包含部署的应用程序和模块名称,如myear / myejbjar / MyBean。如果这是false,那么默认绑定名称就是bean的名称。

 

      重要

      IIOP调用只能使用EJB 2 bean完成。 JBoss EAP 7.0中的IIOP不支持EJB 3 bean。

 

      启用IIOP

      要启用IIOP,您必须安装IIOP OpenJDK ORB子系统,并且ejb3子系统配置中存在<iiop />元素。发行版附带的standalone-full.xml配置都启用了这两个配置。

      IIOP在服务器配置文件的iiop-openjdk子系统中配置。

<subsystem xmlns="urn:jboss:domain:iiop-openjdk:1.0">

      使用以下管理CLI命令访问和更新iiop-openjdk子系统。

/subsystem=iiop-openjdk

      IIOP元素具有两个控制服务器默认行为的属性。

<subsystem xmlns="urn:jboss:domain:ejb3:1.2">
  ...
  <iiop enable-by-default="false" use-qualified-name="false"/>
  ...
</subsystem>

      以下管理CLI命令在ejb3子系统下添加<iiop>元素:

/subsystem=ejb3/service=iiop:add(enable-by-default=false, use-qualified-name=false)


      创建使用IIOP进行通信的EJB

       以下示例演示如何从客户端进行远程IIOP调用:

      1.在服务器上创建一个EJB 2 bean:

@Remote(IIOPRemote.class)
@RemoteHome(IIOPBeanHome.class)
@Stateless
public class IIOPBean {
    public String sayHello() throws RemoteException {
         return "hello";
    }
}

      2. 创建一个home实现,它具有强制方法create()。 此方法由客户端调用以获取远程接口的代理来调用业务方法:

public interface IIOPBeanHome extends EJBHome {
    public IIOPRemote create() throws RemoteException;
}

      3. 创建用于远程连接到EJB的远程接口:

public interface IIOPRemote extends EJBObject {
    String sayHello() throws RemoteException;
}

      4. 通过在META-INF中创建一个描述符文件jboss-ejb3.xml来引入远程调用的bean:

<?xml version="1.0" encoding="UTF-8"?>
<jboss:ejb-jar xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
               xmlns="http://java.sun.com/xml/ns/javaee"
               xmlns:iiop="urn:iiop"
               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
                  http://java.sun.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-ejb3-spec-2_0.xsd
                  urn:iiop jboss-ejb-iiop_1_0.xsd"
               version="3.1"
               impl-version="2.0">
    <assembly-descriptor>
        <iiop:iiop>
            <ejb-name>*</ejb-name>
        </iiop:iiop>
    </assembly-descriptor>
</jboss:ejb-jar>

      注意

      打包的bean以及JAR文件中的描述符现在可以部署到JBoss EAP容器。

 

      5.在客户端创建一个上下文:

System.setProperty("com.sun.CORBA.ORBUseDynamicStub", "true");
final Properties props = new Properties();
props.put(Context.PROVIDER_URL, "corbaloc::localhost:3528/JBoss/Naming/root");
props.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.iiop.naming:org.jboss.naming.client");
props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.cosnaming.CNCtxFactory");
props.put(Context.OBJECT_FACTORIES, "org.jboss.tm.iiop.client.IIOPClientUserTransactionObjectFactory");

     注意

     客户端将需要将wildfly iiop openjdk库添加到其类路径中。客户端还可能需要将org.wildfly:wildfly-iiop-openjdk工件添加为Maven依赖关系。

 

       6. 使用上下文查找缩小对IIOPBeanHome主界面的引用。 然后调用home接口create()方法来访问远程接口,这样就可以调用它的方法:

try {
    Context context = new InitialContext(props);

    final Object iiopObj = context.lookup(IIOPBean.class.getSimpleName());
    final IIOPBeanHome beanHome = (IIOPBeanHome) PortableRemoteObject.narrow(iiopObj, IIOPBeanHome.class);
    final IIOPRemote bean = beanHome.create();

    System.out.println("Bean saying: " + bean.sayHello());
} catch (Exception e) {
    e.printStackTrace();
}

          原文地址:https://access.redhat.com/documentation/en-us/red_hat_jboss_enterprise_application_platform/7.0/html/developing_ejb_applications/invoking_session_beans 







阅读更多
文章标签: java ee EJB
个人分类: JavaEE EJB
上一篇【EJB】Developing EJB Applications -- Chapter 4(消息驱动Bean)
下一篇【EJB】Developing EJB Applications -- Chapter6(EJB应用安全)
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭