摘要
本文介绍了JBOSS EAP 6.1 EJB 客户端的两种实现方式。由于客户端是通过JNDI的方式来唤起EJB,所以接下来深入的讨论了JBOSS EAP 6.1遵循的JEE6中EJB JNDI,对JNDI的变化以及这些变化所带来的利弊进行介绍。
EJB客户端的实现
本节介绍JBOSS EAP 6.1中EJB客户端的实现。实现有如下4步:首先,创建一个JAVA项目。
接着,为项目引入一个jar包jboss-client.jar,该包可以在jboss-eap-6.1\bin\client的位置获得。用于获得JBOSS EAP中的JNDI上下文。
然后,在代码最外层(对应project目录的话,放到src下),配置属性文件: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
最后是客户端的代码实现。具体实现的方法有两种,在这首先介绍官方开发者文档中的方法,然后介绍一个相对通用一些的方法。
方法一:org.jboss.ejb.client.naming
#JBoss_Enterprise_Application_Platform-6.1-Development_Guide-en-US文档中介绍了如下的方法来唤起一个EJB。
直接上客户端代码:
public class HelloWorldClient {
public static void main(String[] args) {
Hashtable<String, String> jndiProperties = new Hashtable<String, String>();
jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
try{
Context context = new InitialContext(jndiProperties);
String ejbPattern = "ejb:";
String appName = "";
String moduleName = "HelloWorld";
String distinctName = "";
String beanName = "HelloWorldBean";
String interfaceName = "com.xx.leo.HelloWorldBusiness";
String jndiName = ejbPattern + appName+"/"+ moduleName+"/" + distinctName+"/"+beanName+"!"+interfaceName;
System.out.println(jndiName); //输出为:ejb:/HelloWorld//HelloWorldBean!com.hp.leo.HelloWorldBusiness
HelloWorldBusiness hello = (HelloWorldBusiness)context.lookup(jndiName);
System.out.println(hello.sayHello()); //输出为:Hello World.
}catch(NamingException e){
e.printStackTrace();
}
}
}
其中引用了client包中的org.jboss.ejb.client.naming命名查询功能。最关键的地方在ctx.lookup(jndiName)方法中jndiName的生成。我们会在下一节中做具体介绍。官方给的这个客户端直接调用某个EJB倒是没有问题,但是如果想做模糊查询,查查看现有JNDI都有哪些的时候,会想到ctx的list(“”)方法。List方法在org.jboss.ejb.client.naming中不适用。
反编译:jboss-client.jar\org\jboss\ejb\client\naming\ejb\EjbNamingContext.java会看到如下的代码:
public NamingEnumeration list(Name name)
throws NamingException
{
throw Logs.MAIN.unsupportedNamingOperation();
}
public NamingEnumeration list(String name)
throws NamingException
{
throw Logs.MAIN.unsupportedNamingOperation();
}
所以在此还提供另一个更加通用的能够list的调用方法。
方法二:org.jboss.naming.remote.client.InitialContextFactory
public class HelloWorldClient2 {
public static void main(String[] args) {
Properties jndiProps = new Properties();
jndiProps.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
jndiProps.put(Context.PROVIDER_URL,"remote://localhost:4447");
// create a context passing these properties
Context ctx;
try {
ctx = new InitialContext(jndiProps);
HelloWorldBusiness o1 = (HelloWorldBusiness) ctx.lookup(
"HelloWorld/HelloWorldBean!com.xx.leo.HelloWorldBusiness");
System.out.println(o1.sayHello()); //输出为:Hello World.
} catch (NamingException e) {
e.printStackTrace();
}
}
}
同样最为关键的部分是还是ctx.lookup(“…”)中的部分,这写JNDI是怎么生成的?有什么规律将在下节中做介绍。
JNDI的变化
JEE6中对EJB JNDI的规范改动非常大,致使原本在JBOSS 4 5 6上的产品想要使用JBOSS EAP 6.1(JBOSS AS 7)都必须对EJB的JNDI的管理这块做全新的设计。直接上规范(JEE6 JSR#318):java:global[/<app-name>]/<module-name>/<bean-name>[!<fully-quali?fied-interface-name>]?stateful
java:app/<module-name>/<bean-name>[!<fully-qualified-interface-name>]?stateful
java:module/<bean-name>[!<fully-qualified-interface-name>]?stateful
其中各部分解释如下:
- <app-name> ear包名称
- <module-name> jar包名称
- <bean-name> bean中指定的名称
- [!<fully-quali?fied-interface-name>]业务接口的全称
- ?stateful: 如果是有状态bean需要添加上?stateful字段。
从前的EJB JNDI name可以完全由Bean来指定,但最新的JEE6中EJB JNDI名称由多个部分组成,Bean中配置的只是其中一项。举个实例:
从前JNDI名称:HelloWorldBean
新的JNDI名称:java:global/HelloWorld/HelloWorldBean!com.xx.leo.HelloWorldBusiness
对应上一节方法二中lookup("")的内容是JNDI的全称。我们没有使用ear包,所以只有jar包名:HelloWorld;Bean名称HelloWorldBean和接口全称com.hp.leo.HelloWorldBusiness。拼到一起就有了ctx.lookup("HelloWorld/HelloWorldBean!com.xx.leo.HelloWorldBusiness");
由规范可以看出如今的EJB JNDI名称不能由代码完全决定,其中还包括了war包,jar包,接口的全称等部分。新JNDI名称带来了两个好处:
- 业务接口全称的暴露,虽然不能像Web service一样直接从WDSL中拿到接口信息,但是全程至少使得我们明白这个客户端要调用的接口具体在什么目录下,以便能够找得到。否则,在从前的JNDI名称中是没法确认客户端是否已经有了EJB的客户端stub的。
- ear包、jar包也写入,这个使得同一个EJB组件少做修改可以挂载N次。我们只需要修改JNDI名称就能测试同一个EJB组件前后两次修改对程序的影响。
我们再来看看上节方法一中的JNDI名称
ejb:/HelloWorld//HelloWorldBean!com.hp.leo.HelloWorldBusiness
这个名称来自JBOSS EAP 6.1规范。JBOSS 写了一套自己的客户端,使用ejb:开头。要调用的话需要实现以下类似于JEE6 中EJB JNDI的规范如下:
ejb:<appName>/<moduleName>/<distinctName>/<beanName>!<viewClassName>?stateful
其中:
- <appName> ear文件名
- <moduleName> jar文件名
- <distinctName> 别名
- <beanName> Bean名称
- <viewClassName> 接口全称
- ?stateful: 如果是有状态bean需要添加上?stateful字段。
代码中给出了JNDI名称拼出来的过程:
String ejbPattern = "ejb:";
String appName = "";
String moduleName = "HelloWorld";
String distinctName = "";
String beanName = "HelloWorldBean";
String interfaceName = "com.xx.leo.HelloWorldBusiness";
String jndiName = ejbPattern + appName+"/"+ moduleName+"/" + distinctName+"/"+beanName+"!"+interfaceName;
新JNDI的麻烦
EJB JNDI的大变为新代码的开发带来了如上节所提及的两个好处之外,也为JNDI名称的管理以及旧系统升级都带来了一些问题。以下抛砖引玉,举例引出在新JNDI下对命名管理引发的问题:【实例:产品中有很多个EJB,有些是同一类别的,但由不同的项目组来开发,在最终界面展示的时候需要列出所有现有的这一类EJB组件。】
这就用到了上文提及的list方法,它可以用来模糊查询容器中现有的EJB。但list有个限制就是JNDI名称按”/” 化分为多个级别,由左像右逐级查询。看看list在新旧JNDI中的表现:
旧JNDI名称自始至终都由代码直接限定,所以第一级可以统一规定一个名称。如:规定第一段为GroupA,不同的小组开发了不同的EJB Bean JNDI名称可规定为
- /GroupA/HelloBeijing
- /GroupA/HelloShanghai
- /GroupA/HelloGuangzhou
- /GroupA/HelloChongqing
新JNDI可能得到的EJB JNDI如下:
- JarPackBeijng/GroupA/HelloBeijing!com.xx.ngoss.xxx.HelloWorldBusiness
- JarPackShanghai/GroupA/HelloShanghai!com.xx.ngoss.xxx.HelloWorldBusiness
- JarPackGuangzhou/GroupA/HelloGuangzhou!com.xx.ngoss.xxx.HelloWorldBusiness
- JarPackChongqing/GroupA/HelloChongqing!com.xx.ngoss.xxx.HelloWorldBusiness
使用list(“JarPack”)可以得到四个EJB,但问题是新的JNDI第一段(包含JarPack的一段)是包名,限制被放到了包的命名中,不能由代码来完全规定,而限定包名在很多情况下是一件很滑稽的事情。特别当这些包由客户来创建的时候,客户按照规范写他们自己的客户端,在代码中有一些限制是可以理解的。但是如果客户自己打个包都需要按照产品的限制来弄,无疑会让客户对产品的用户体验方面打上一个大问号。