EJB3中JNDI调用SessionBean的解决方案

OECPOpen Enterprise Components Plateform,开放的企业级组件平台)项目的架构体系中,各业务组件需要有运行在不同的EJB容器中的能力,在组件层构建起一个业务处理的分布式集群环境。

    我们知道在客户端我们需要通过JNDI来调用EJB容器中的session bean,在EJB3中,获得JNDI上下文主要有两种方式。(默认采用jboss的获取方式)

1、通过程序编码的方式

public static InitialContext getInitialContext() throws NamingException{
   Properties p = new Properties();
  p.put(Context.INITIAL_CONTEXT_FACTORY,

"org.jnp.interfaces.NamingContextFactory");
   p.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
   p.put(Context.PROVIDER_URL, "jnp://localhost:1099");
   return new InitialContext(p);
}

客户端通过InitialContext ctx = getInitialContext();的方式来获取上下文。

2、通过配置属性文件的方式

在应用的src下新建一个jndi.properties的资源文件(注意文件名必须是jndi.properties

内容:

java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=localhost

客户端只要通过下面的方法就能获得上下文对象了

InitialContext ctx = new InitialContext();

   为了方便在程序开发的过程中的测试,我们一般情况下会封装一个获得JNDI上下文的工具类来使用。比如我们构建的一个如下的EJBFinder.java的类:

public class EJBFinder {

 

    public static InitialContext getInitialContext() throws NamingException{

       Properties p = new Properties();

       p.put("server", "jboss");

       p.put(Context.INITIAL_CONTEXT_FACTORY,

              "org.jnp.interfaces.NamingContextFactory");

       p.put(Context.URL_PKG_PREFIXES,

              "org.jboss.naming:org.jpn.interfaces");

       p.put(Context.PROVIDER_URL, "jnp://localhost:1099");

       InitialContext ctx = new InitialContext(p);

       return ctx;

    }

   

    public static Object findEJB(String ejbname) throws NamingException{

       return getInitialContext().lookup(ejbname);

    }

}

这样的一个工具类给我们测试EJB组件提供了很大的方便,不用我们再去重复的去构建JNDI上下文的环境。但是当EJB组件开发完成后,正式部署到正式环境中,客户端调用EJB组件怎样去调用呢?现在我们分析这其中出现的问题。

1、 客户端不在同一个JVM环境中,应该怎样处理?

2、 Session Bean对于客户端是隐藏的,开发客户端的程序员怎么能非常方便的获得Session Bean的绑定名?怎么能方便的得到JNDI上下文呢?

3、 EJB组件分别部署在不同的服务器上,我们怎么能方便的调用?

4、 EJB组件如果运行在不同的EJB容器中,我们怎么能方便的得到对应的JNDI上下文环境?

针对这些问题,我们逐步的去分析解决。

客户端不在同一个JVM中这是普遍出现的情况,我们可能将web客户端运行在Tomcat上,而EJB运行在JBoss容器中,对于这种情况,我们如果采用上面介绍的第一种情况来获得JNDI上下文就非常不可行,因为EJB的环境的变化会导致太多程序代码的改动,为日后的维护埋下了隐患。所以只能采用第二种方式,将JNDI上下文信息配置在属性文件中,即使以后EJB的环境更换,也可以通过修改配置文件来实现这种改变。

   通过JNDI的方式来调用EJBSession Bean,必然少不了为Session Bean绑定JNDI名。默认的情况下(JBoss),容器会以实现类的类名作为JNDI名。比如getIntialContext().lookup(“LoginBean/remote”); LoginBean就是Login接口的实现类的名字。

@Stateless

@Remote(Login.class)

public class LoginBean implements Login{}

如果这样去做的话,我们开发客户端的程序员就必须要知道这个Session Bean接口的实现类是什么名字,这不是一种好的方式,违背了我们隐藏实现的编程思想。才开始大家建议,约定Session Bean的命名格式为接口+Bean后缀,这是一个比较可行的解决方案。但是一个约定好的命名格式,只能给我们的是一个已知的字符串,我们利用这个字符串所能做的事情太少了。我们利用@Stateless中的name属性,可以为Session Bean起个别名,这个东西应该很好的利用起来。我们再一次讨论,对于客户端来说,什么是透明的呢?无疑是接口,我们能不能把接口的名字作为Session Bean的别名呢。这个主意不错,大家一致通过,看了网上的一些例子,好多人也是采用这种方式。但是也有不好的方面,如果一个Session Bean实现多个接口就不是很好处理了。我们只能再次约定,一个Session Bean只能对应着一个远程接口。这样我们只知道接口就能方便的调用EJB了。

但问题又接踵而至,我们使用EJB作为组件开发技术,很大程度上是利用它强大的分布式计算的能力,这就要求每个EJB组件有可能运行在不同的服务器上,我们调用这些EJB组件,只采用一套上下文配置的方式是非常不可行的。我们需要为每个组件配置各自的JNDI上下文环境。怎么去做呢?在客户端我们为每个组件建立一个Properties文件,里面包含了该组件的JNDI配置。我们调用不同的组件时,我们就去获取该组件的JNDI配置,这已经很好的解决了相关的问题,完全可以适应EJB分布式的环境,但是有没有更好的方法让用户不再去选择每个组件的配置,让他们感觉和本地调用一样简单呢。于是我们在Session Bean接口上做了一些文章,通过自定义annotation的方式,来为每个接口标志相关的组件信息,通过程序解析annotation来获得组件的信息来进行相关的处理。我们定义一个@ComponentInfo的元数据来标注组件的相关信息。

@Target( { TYPE })

@Retention(RetentionPolicy.RUNTIME)

public @interface ComponentInfo {

  String name();

  String description() default "";

}

  name属性为组件的名字,description顾名思义就是组件的描述,通过作用在每个接口上的这个annotation,我们就可以获得组件名称。我们约定客户端的JNDI配置的属性文件的文件名要和这里的组件名称对应,这样我们就可以自动找到该组件需要的JNDI配置。

为了方便EJB组件的开发,我们可以为每个EJB组件做一个全局的接口。将该annotation作用在最上层父类的TYPE上,而该组件所有的Session Bean接口都继承这个接口。

问题都解决了吗?我们不断地寻找问题。我们在这里不得不对我们尊敬的SUN一口恶骂,骂它做事情什么时候能做的彻底,为什么不统一标准,让所有的EJB容器都采用同样格式的JNDI获取方式。现在问题来了,不同的容器存在自己特有的lookup实现方式,太郁闷了,这样就要将EJB组件和容器绑死了,这为以后EJB组件的移植设置了巨大的障碍。我们思考着,这个问题似曾相识,对,用工厂模式来解决这种移植性的问题,对不同的实现通过工厂的方式进行集中管理,从而方便系统的移植。说干就干,我们建立一个公共的接口:

public interface JNDIFinder {

           public Object lookup(InitialContext ctx, Class clazz)throws NamingException;

}

我们为每个容器建立自己的JNDI查找实现类:JbossJNDIFinderImpl.javaWeblogicJNDIFinderImpl.java

 

然后再建立JNDIFinder的工厂来进行管理和获取:JNDIFactory.java

public static JNDIFinder getFinder(String server) {

              if (JBOSS_SERVER.equals(server)) {

                  return new JbossJNDIFinderImpl();

              } else if (WEBLOGIC_SERVER.equals(server)) {

                  return new WeblogicJNDIFinderImpl();

              }

              return new JbossJNDIFinderImpl();// 默认返回jboss的实现

}

getFinder(String server)中的server我们可以将它配置到我们的JNDI配置文件中:

server=jboss

java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory

java.naming.factory.url.pkgs=org.jboss.naming:org.jpn.interfaces

java.naming.provider.url=jnp://localhost:1099

万事俱备只欠东风了,我们最后把这些要统一封装成一个最简单方法来进行调用。我们建立一个工具类JNDIUtil.java,在该类中,我们向外暴露一个静态方法:

public static Object lookup(Class clazz) throws NamingException

 

大功告成了,现在只需要一个简单的调用Login login =(Login)JNDIUtil.lookup(Login.class); 就解决了上面提出的相关问题,实现了跨EJB服务器的分布式EJB组件的调用。(后面将附上该方案的相关源代码)

当然,这个解决方案还需要经过实际开发的检验,我们也将不断地优化该方案,也希望更多人讨论交流,提出宝贵的意见,目的是为了更好的解决该问题。

 

原文:

           源代码:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值