折腾了几天终于比较满意地(Least-intrusive,Most-portable)解决了这个问题,学到了不少东西。特分享如下。


基本环境: Mobicents SipServlet 3.0.0-Snapshot, JBoss AS 7.13, JavaEE 6

需求:用JavaEE6技术重构一个基于Mobicents做的VOIP的项目,本文只分享用CDI和EJB3.1的注解实现依赖注入的经验。

问题:如何便捷地将Mobicents的SipFactory注入到普通的bean(CDI管理)中,或者EJB3.1的bean中?


尝试1:直接@Resource。

public class MySipServlet extends SipServlet {
    @Resource private SipFactory sipFactory;
    ...
}
@Singleton
public class MyBean {
    @Resource private SipFactory sipFactory;
}

结果:在SipServlet(MySipServlet)中会成功注入。一般的bean中(MyBean)不能成功注入,时而有NPE。

分析:经过艰苦卓绝的调查取证,我认为我找到了部分原因... 按Mobicents官方的说法,SipFactory只有在SipServlet容器Context启动时才会构建,而SipServlet容器是随着第一个SipServlet的初始化启动的。(注意默认都是Lazy Loading的)。也就意味着有可能上面的MyBean(即便为Stateless)需要注入和使用SipFactory的时候,它还没有准备好!即便有所谓的三种获取方式(JNDI调用,mappedName,和@Resource),上面的问题还是解决不了。尤其是如果需要加上@Startup给一个@Singleton的时候,它实例化的时后SipFactory肯定没准备好。


尝试2:基于上面分析,只能放弃上面三种方式。接下来自然就想到监听SipServletContext的事件,等它初始化完成SipFactory肯定就可以用了。

public class SipServletFacade1 implements SipServletListener {
    private SipFactory sipFactory;
}
public class SipServletFacade2 extends SipServlet implements SipServletListener {
    private SipFactory sipFactory;
}

结果:SipServletFacade1貌似很简单但是好像Mobicents实现的容器不接受(只有SipServlet才能作为SipServletListener?),所以失败。SipServletFacade2侵入性太强(SipServlet应该在展现层而不是到业务层),所以还是早早放弃吧!

分析:注意到此时我们有了Facade模式和Event/Listener的思想。我们想用POJO模式的Facade封装SipServlet容器的工具类SipFactory,一方面屏蔽它的依赖注入方式,另一方面还有别的Facade的好处:比如简化实用,封装异常等等。可惜,可惜,上面的方式行不通...


最终尝试:JavaEE 6  javax.enterprise.event.*  !

分析:注意使用JavaEE 6的Event/@Observes大大简化监听者模式的实现而且注入性小。实现了低耦合。

public class MySipServlet extends SipServlet() {
@Resource SipFactory sipFactory;
@Inject private Event myEventEmitter;
...
@Override
public void init() {
    myEventEmitter.fire(new MyEvent(sipFactory));
}
public class SipServletFacade {
    private SipFactory sipFactory;
    public void sipServletInit(@Observes MyEvent myEvent) {
        sipFactory = myEvent.getSipFactory();
    }
}