在EJB3.1之前在会话bean的种类中是没有单例会话bean的,有的只是有状态会话bean以及无状态会话bean。毋庸置疑无状态会话bean以其优秀的性能被普遍使用,但是人们发现在无状态会话bean在使用过程中有两个最常值得关注的问题。按照官方一点儿的话说那就是一、bean池的感知开销。二、无法通过静态字段共享状态。其中第二点很好理解,因为无状态会话bean在每次请求的时候都需要从实例池中拿到一个实例对请求进行服务所以不可能存在像普通java变量那样通过共享静态字段共享不同请求之间的状态。第一点所谓的感知开销是指客户端发起请求的时候bean池不知道还有多少个实例可用,bean池所做的就是将请求放在一个队列中然后等待可用的实例,然后将这个实例拿出来对请求进行服务。
单例会话bean的出现实现了单个共享的bean实例,为上面两个问题提供了解决方案,这个共享bean既可以并发访问(解决第一个关于bean池感知开销的问题),也可以作为一种共享状态的机制(共享不同会话之间的状态)。单例会话bean与无状态会话bean的生命周期回调是相同的但生命周期是完全不同的,为了能够进行同步在单例会话bean中还了复杂的开发人员控制锁(developer-controlledlocking)。
与其他会话bean不同,单例会话bean在应用程序初始化期间迅速创建单例,同时在应用程序关闭之前一直存在。可以这么认为,当应用程序启动这个单例就一直存在知道服务器停止。也就是说单例会话bean一旦创建了,它就将在容器删除它之前一直存在,无论在业务方法执行过程中是否发生任何异常。这是它与其他会话bean类型之间的一个关键差异,因为永远不会在系统异常事件中重新创建这种bean实例。
因为单例会话bean的声明周期很长,并且能够共享实例,所以常见应用程序状态的理想储存位置就落在了它的头上,无论是只读(read-only)状态还是读写(read-write)状态。为了确保访问这种状态,单例会话bean提供了许多并发选项,它们取决于应用程序开发人员的需要。考虑到性能,方法可能完全不同步,或者由容器自动地锁定和管理。
1.定义单例会话bean
和无状态和有状态会话bean的模型一样,单例会话bean使用@Singleton注解来定义。单例会话bean可以包括一个本地业务接口或者使用一个无接口视图。下面代码显示了一个简单的具有无接口视图的单例会话bean,用于跟踪Web站点的访问次数。
@Singleton
public class HitCounter {
int count;
public void increment() { ++count; }
@Lock(LockType.READ)
public int getCount() { return count; }
public void reset() { count = 0; }
}
如果将上面程序中的HitCounterbean与前面定义的无状态与有状态会话bean进行比较,那么立刻可以看到两个差异。与无状态会话bean不同,它存在计数字段形式的状态,用于捕获访问计数(保存了会话的状态,而且在不同会话之间是共享的)。与有状态会话bean也不同,没有包含@Remove注解以标识用于完成会话的业务方法(表明单例会话bean不会被销毁)。
默认情况下,容器将管理业务方法的同步以确保不会发生数据破坏。在此例中,这意味着对于bean的所有访问进行序列化,从而保证在任何时间只有一个客户端调用该实例的业务方法。
单例会话bean的生命周期关系到整个应用程序的生命周期。容器决定何时创建单例实例的时间,(如果该bean同时包括@Startup注解,那么应用程序将会强制在启动时迅速初始化这个单例会话bean的实例)。容器当然也可以创建不被@Startup注解标记的在系统应用程序启动时初始化的singleton,但这是特定于不同的服务器供应商的,JavaEE中没有做表述。
当多个单例会话bean互相依赖时,需要告知容器对它们进行实例化的顺序。这时可以通过在bean类中的添加@DependsOn注解来实现,其中列出了其他必须提前创建的单例会话bean的名称(如果多个记得逗号隔开)。