关于EJB异步调用遇到的问题解决

近日看到ejb异步调用相关文章,突然有兴趣试下,做个示例程序都是磕磕绊绊的,所幸最后都解决了,在这里记录下遇到的问题和解决版本,希望对大家有所帮助。

  1. 异步ejb?
    Asynchronous Session BeanEJB 3.1 规范定义可以采用异步的机制,将原本需要长时间执行的工作并行化。无论客户端是本地还是远程的,都可以声明 EJB 的业务方法与客户端异步执行。缺省情况下,业务方法相对于客户端是同步的。
    这个新特性主要为了提高性能和可扩展性,允许客户端请求由多个线程 (thread) 处理。客户端可以调用多个异步的 EJB 方法利用多线程并发处理工作,同时客户端线程也能继续处理请求。
    有两种方式的异步 EJB 方法调用:

    • ”fire and forget”:声明异步方法返回值为 void,客户端无法知道异步方法调用何时结束。这种方式适用于执行一些“可有可无”、其是否完成无关紧要的场景。通过这种方式的异步请求,主线程上重要操作的性能因此而得到提高。
    • “fire and return results”:声明异步方法有个 Future 类型的返回参数。该异步方法执行前,会立即先返回一个 Future 实例给应用程序客户端,然后客户端可以用 Future 实例的 get() 方法来检查异步方法完成与否。
  2. 示例程序
    这里只是为了测试ejb的远程调用,所以示例程序写的比较简单。
    远程接口类 RemoteInface.java:

public interface RemoteInface {
    public String sayHello(String str);
    public void sayHello1(String str);
    public void sayBye(String str);
    public Future<String> sayBye1(String str);

}

远程接口实现类 RemoteServer.java:

@Remote
@Stateless(mappedName="Remote")
public class RemoteServer implements RemoteInface {

    /* (non-Javadoc)
     * <p>Title:sayHello</p>
     * <p>Description:</p>
     * @param str
     * @return
     * @see com.css.sword.ejb.RemoteInface#sayHello(java.lang.String)
     */
    @Override
    public String sayHello(String str) {
        // TODO Auto-generated method stub
        System.out.println("接收到的信息为:"+str+new Date());
        return str;
    }
    public void sayHello1(String str) {
        // TODO Auto-generated method stub
        System.out.println("接收到的信息为:"+str+new Date());
    }
    @Asynchronous  
    public void sayBye(String str) {
        // TODO Auto-generated method stub
        System.out.println("接收到的信息为:"+str+new Date());
    }

    @Asynchronous  
    public Future<String> sayBye1(String str) {
        // TODO Auto-generated method stub
        System.out.println("接收到的信息为:"+str+new Date());
        return new AsyncResult<String>(str);
    }

可以看到,异步ejb实现很简单,只需在想异步调用的方法上加上注释 @Asynchronous即可
注: @Asynchronous这个注解是ejb3.1以上提供的,如果编译时找不到这个注解,注意ejb包的版本!!!

客户端测试程序 EjbClient.java—在weblogic11g下测试:

public class EjbClient {

    public static void main(String[] args) throws InterruptedException, ExecutionException{
        String connectFactory = "weblogic.jndi.WLInitialContextFactory";
        String url = "t3://127.0.0.1:7001";

        try {
            Hashtable<Object,Object> env = new Hashtable<Object,Object>();
            env.put(Context.INITIAL_CONTEXT_FACTORY, connectFactory);
            env.put(Context.PROVIDER_URL,url);
            Context ctx =new InitialContext(env);
            RemoteInface inface = (RemoteInface) ctx.lookup("Romate.EjbServer");

            long startTime = System.currentTimeMillis();
            System.out.println(inface.sayHello("你好,世界"));
            inface.sayHello1("你好,世界");
            inface.sayBye("你好,世界"+new Date());
            Future<String> futrue=inface1.sayBye1("你好,世界"+new Date());
            System.out.println(futrue.get());
            long endTime = System.currentTimeMillis();
            System.out.println(endTime-startTime);
        } catch (NamingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

前面几个方法调用都没有问题,但是调用inface1.sayBye1时总报错:
java.io.NotSerializableException: javax.ejb.AsyncResult如下图所示:
这里写图片描述

可以看出问题主要原因是AsyncResult没有序列化,可是这是ejb3实现类,主要是为了远程调用,序列化的问题不可能没有想到。起初我猜想是不是包的版本太低,后来下了几个包后发现代码没有变化,这时我在一篇文章中看到这么一句话:
注意:javax.ejb.AsyncResult 对象是 Future 的一个简单实现,只用于向容器传送对象,该对象不会被传到客户端。
所以我猜想,可能是容器的问题,我使用的是weblogic11g,可能该版本还没有完全兼容javaee6。也有网友给出了这样的结论
WebLogic 11g is fully Java EE 5 compliant [1], but WebLogic 11gR1 PS3 (10.3.4) included some Java EE 6 technologies [2,3]
所以我决定换个应用服务器验证下,WebLogic 12c已经完成兼容了Java EE 6,但是想到weblogic付费及一系列问题,所以我使用了更轻量级的JBoss来测试,本人第一次使用jboss,有不足之处希望请大家批评指正。

JBoss版本jboss-as-7.1.0.Final(7.0以上完全兼容了Java EE 6)。第一次使用jboss发现果然很简单,这里就不赘述那些安装配置了,在部署好ejb后,继续运行客户端,还是遇到了问题……,这里顺便记录下JBoss调用ejb的过程。
客户端添加代码用来获取上下文:

/**
     * @name:getJboss7Context
     * @Description: Jboss6、7通过jndi查找ejb的方法和jboss5有点不一样,这里示例的是jboss7
     * @author yuanxj
     * @date 2017-1-9 上午11:01:28
     * @param appName   这里是.EAR包的名称,如果你打包成JAR发布的话,这里则留空
     * @param moduleName 这里是你发布的JAR文件名(.jar,.war),如helloworld.jar,则这里应该为helloworld。去掉后缀即可
     * @param distinctName   如果没有定义其更详细的名称,则这里留空
     * @param beanClass 实现类
     * @param InterfaceClass 接口类
     * @return
     * @throws NamingException 
     */
    private static  Map<String,Object> getJboss7Context(String appName,String moduleName,String distinctName,Class beanClass,Class InterfaceClass) throws NamingException{
        Map<String,Object> resultMap = new HashMap<String,Object>();
        final Hashtable<String, Object> jndiProperties = new Hashtable<String, Object>();
        jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
        //jndiProperties.put("jboss.naming.client.ejb.context", true);

        final Context context = new InitialContext(jndiProperties);
        final String beanName = beanClass.getSimpleName();           //这里为实现类的名称    
        final String viewClassName = InterfaceClass.getName();        //这里为你的接口名称

        /*
         *      For stateless beans:  
                ejb:<app-name>/<module-name>/<distinct-name>/<bean-name>!<fully-qualified-classname-of-the-remote-interface>  
                For stateful beans:  
                ejb:<app-name>/<module-name>/<distinct-name>/<bean-name>!<fully-qualified-classname-of-the-remote-interface>?stateful  
         * */
        String jndi = "ejb:" + appName + "/" + moduleName + "/"  
                + distinctName + "/" + beanName + "!" + viewClassName;   
        System.out.println("jndi名为:"+jndi);
        resultMap.put("context", context);
        resultMap.put("jndi", jndi);
        return resultMap;
    } 

要注意JBoss5和JBoss6、7调用方法不一样,这里只示例JBoss7使用。

客户端改下:

public static void main(String[] args) throws InterruptedException, ExecutionException{
        try {
            Map<String,Object> jbossMap=getJboss7Context("","myEjbTest","",RemoteServer.class,RemoteInface.class);
            Context ctx1 = (Context) jbossMap.get("context");
            RemoteInface inface1 = (RemoteInface) ctx1.lookup((String)jbossMap.get("jndi"));
            inface1.sayHello("你好,世界");
            inface1.sayHello1("你好,世界");
            Future<String> futrue=inface1.sayBye1("你好,世界"+new Date());
            System.out.println(futrue.get());
            long endTime = System.currentTimeMillis();
            System.out.println(endTime-startTime);
        } catch (NamingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

在客户端引入包:jboss-client-7.1.0.Final.jar(在jboss安装目录/bin/client下),
创建 jboss-ejb-client.properties文件

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

默认的remote端口是4447。
测试通过,可以通过future.get()取到值。

可能会遇到的问题:

  1. No EJB receiver available for handling [appName:,modulename:myEjbTest,distinctname:] combination

这里写图片描述
原因1:host和端口没有写对,记得端口是远程端口
原因2:工程中有其它jndi文件,加载时覆盖了jboss-ejb-client.properties文件(可以跟断点查看context内容)
原因3:没有jboss-ejb-client.properties配置文件

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值