JBOSS EAP 6 系列四 EJB客户端--让人又爱又恨的JNDI

摘要

        本文介绍了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();
		}
	}
}


        其中引用client包中的org.jboss.naming.remote.client.InitialContextFactory命名查询功能,同时可以在代码中配置remote的链接地址。需要注意的是,依然需要jboss-ejb-client.properties这个文件,配置方式与方法一中的一样。其中也指定了host和port,虽然是强制有default的配置,但是不影响代码中remote到另外的ip:port中,实际链接的远端地址可以在代码中配置。

同样最为关键的部分是还是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名称带来了两个好处:
  1. 业务接口全称的暴露,虽然不能像Web service一样直接从WDSL中拿到接口信息,但是全程至少使得我们明白这个客户端要调用的接口具体在什么目录下,以便能够找得到。否则,在从前的JNDI名称中是没法确认客户端是否已经有了EJB的客户端stub的。
  2. 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

        使用list(“GroupA”)就可以得到所有的这4个EJB的JNDI名称。


新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的一段)是包名,限制被放到了包的命名中,不能由代码来完全规定,而限定包名在很多情况下是一件很滑稽的事情。特别当这些包由客户来创建的时候,客户按照规范写他们自己的客户端,在代码中有一些限制是可以理解的。但是如果客户自己打个包都需要按照产品的限制来弄,无疑会让客户对产品的用户体验方面打上一个大问号。


        以上是一个小实例引出新JNDI为EJB的命名管理所带来的问题,针对这个问题在实际开发中,我们没有设置这样的对客户的限制,而是采取回归原来的管理方式。具体实现是采用了牺牲性能的办法,把所有的JNDI从第一级到最后一集全都查询出来,然后再从JNDI全称中使用原来的逻辑来辨别同类或者区分异类。

总结

        本文首先对Jboss EAP EJB客户端的实现做了简单的介绍。随后介绍了EJB JNDI在JEE6中的变化,它的构成和实际应用。正因为这个JNDI的变化,使得客户能够看到他们要的接口是否已经存在,存在于什么位置,并且能够方便的测试稍作修改之后的新旧Bean带来的变化,所以想到了使用遵循JEE6新规范的JBOSS EAP 6。JNDI的变化带来了一系列问题,文中举了个实例来说明JNDI带来的麻烦,来说明JNDI的变化使得EJB命名的管理方面没有从前那样从容。

你可能感兴趣的:(jboss,JNDI,EAP,jboss7,ejb客户端)