折腾了几天终于比较满意地(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<MyEvent> 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(); } }