BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

Java EE 6:EJB 3.1的变化引人注目

| 作者 Josh Long 关注 7 他的粉丝 ,译者 张龙 关注 14 他的粉丝 发布于 2010年2月18日. 估计阅读时间: 19 分钟 | QCon上海2018 关注大数据平台技术选型、搭建、系统迁移和优化的经验。

Enterprise Java Bean 3.0(EJB 3)规范为Java企业级开发画上了浓墨重彩的一笔,规范的制订是非常透明的,很多想法都来源于社区。它代表了更加一致的服务模式,大量使用POJO的同时降低了复杂性。Java 5的注解间接地成就了这种模式,在使其变得更加强大的同时也减少了开发者的工作量。对于各种新的解决方案来说,EJB 3抛弃了那些差劲的决策,因此会对那些曾经回避EJB的开发者产生极大的吸引力。EJB Entity Beans不见了,取而代之的是JPA Entity。EJB 2.1及更早版本所需的大量Java类和接口不见了。默认的“约定优于配置”得到了广泛的应用,这样人们上手就会非常快。EJB 3.0是一场真正的革命。

如果说EJB 3.0是一场革命,那么EJB 3.1则是一种进化。EJB 3.1的一些新特性似乎应该放到EJB 3.0中,大家应该原谅规范制定者所持有的谨慎态度——让80%的内容能够运转良好要比废弃掉规范中某个差劲的模式要好很多,宁缺毋滥。当然了,其发布时机还是不错的。虽然提供了大量的新特性,但EJB 3.1规范及所有的向后兼容和补充说明加起来一共才626页,比10年前的EJB 2.1规范少了14页。

我们将在本文介绍这些新特性及其使用方式。

EJB 3.1的新特性

EJB 3.1最大的变化并不是向平台增加了很多新特性,而是简化了平台的使用。其中一些扩大了用户所用API的范围以提升灵活性;另一些只是带来了更多的灵活性而已。

Singleton

Singleton是一种新型的Session Bean,提供了一个额外的保证:每个JVM上的Bean只会被创建一次。该特性在很多地方都有用武之地,比如说缓存。此外还保证每个资源只有一个共享视图存在,而应用服务器并没有提供该特性。无需持久化的有价值的数据可以通过简单存储实现,因为重新创建的代价可能会很大。来看一个具体的示例吧。假设其他地方已经创建好了User实体(或许使用了JPA 2.0)。

@javax.ejb.Singleton 

public class ChatRoom { 

   private java.util.Map<User,Collection<String>> userComments; 

   @PostConstruct 
   public void setup (){ 

     userComments = new java.util.concurrent.ConcurrentHashMap<User,Collection<String>>(); 
     /* ...*/
   }
   public void join(User usr){ /* ...*/ }
   public void disconnect(User usr){  /* ...*/ }

   public void say(String msg){ /* ...*/  }
}

所有的客户端都可以通过ChatRoom实例的引用来更新同一个可变的状态。可以保证的是客户端所获得的实例是一样的。由于这是一个Session Bean,因此与其他Session Bean一样都是线程安全的。

@javax.ejb.EJB 
private  ChatRoom  chatRoom ; 

 /* ... */

chatRoom.join(myUser) ;
chatRoom.say( "Hello, world!");
chatRoom.disconnect();

设计Singleton的目的就是用于并发访问。规范赋予了开发者复杂的控制手段来解决并发访问问题。我们可以通过容器以声明的方式指定某种访问类型,该行为是默认的;可以通过注解@javax.ejb.ConcurrencyManagement(CONTAINER)显式指定使用容器管理的并发手段。如果想对Bean进行更多的控制,请使用@javax.ejb.ConcurrencyManagement(BEAN)。凭借容器管理的并发手段,我们可以在方法或类层次上指定访问类型。可以在默认情况下于类层次上使用@javax.ejb.Lock(WRITE)注解以保证所有的业务方法都是可序列化的,然后针对特定的“只读”方法再进行优化,这么做不会产生任何的副作用。对于只读方法需要使用注解@Lock(READ)。对于@Lock(WRITE)注解所修饰方法的所有访问都是可序列化的,同时会阻塞客户端的访问直到前一个访问完成,或是出现超时的情况。可以通过@AccessTimeout注解指定超时的时间,该注解需要一个java.util.concurrent.TimeUnit值。现在我们可以使用这种并发控制了,先删除之前的ChatRoom实现代码。

@javax.ejb.Singleton 
@javax.ejb.Lock(WRITE)
public class ChatRoom { 

   private java.util.Map<User,Collection<String>> userComments; 

   @PostConstruct 
   public void setup (){ 
     userComments = new java.util.concurrent.ConcurrentHashMap<User,Collection<String>>(); 
     /* ...*/
   }

   public void join(User usr){ /* ...*/ }
   public void disconnect(User usr){  /* ...*/ }
   public void say(String msg){ /* ...*/  }

   @javax.ejb.Lock(READ)
   public int getCountOfActiveUsers(){ /* ... run through the map and count ... */ } 
}

显然现在这个ChatRoom实现还是有问题的:一旦用户和帖子的数量超出了应用服务器所能承受的内存极限就会出现内存不足的问题。可以使用过期(expiration)机制来解决这个问题。为了能说的明白点,假设有一个垃圾收集器周期性地检查ChatRoom的帖子并根据LRU原则(或是使用JPA和EntityManager.persists,然后再销毁)销毁过期的聊天数据。

EJB Timer

从EJB 2.1开始EJB就拥有一个Timer机制,然而遗憾的是该机制总是使用毫秒间隔数,用起来也十分不爽。EJB 3.0对此进行了一些改进,但本质上却没有什么改变,还是基于时间间隔。因此,如果想在每周的开始执行一项活动就比较难办了。EJB 3.1对此又进行了一些改进,提供了一种声明式、灵活的定时器服务并参考了其他调度器,如Quartz和Flux。对于大多数简单的调度来说(包括CRON风格的调度),EJB 3.1是一个完美的解决方案。再来看看ChatRoom吧,我们想周期性地回收旧数据。

@javax.ejb.Singleton 
public class ChatRoom {

   private java.util.Map<User,Collection<String>> userComments;
        
   @javax.ejb.Schedule(minute="1", hour="*") 

   public void cleanOutOldDataFromChatLogs() { 
      /** ... not reprinting all the existing code ... */
   }
}

我们编写了一个方法来快速检查数据,如果必要的话就会删除旧的聊天记录,这样一切就尽在掌握之中,不会再出现内存吃紧的问题了。这里使用了声明式模型以保证方法每隔一小时都会运行,当然了,我们仍然从EJB 3.0中注入了TimerService。

无接口的视图

EJB 3.0中的Bean至少要实现一个接口(Local或是Remote的业务接口),接口用作Bean对于客户端的视图。虽然间接地使用接口是个很强大的技术,但有时会使事情变得复杂。在EJB 3.1中可以编写没有接口的Bean。客户端所看到的视图就是类所公开的public方法。

@javax.ejb.Stateless 
public class Calculator { 

  public float add ( float a , float b) { 
    return a + b; 
  } 

  public float subtract (float a , float b){ 

    return a - b ; 
  }

} 

该Bean的客户端可以通过正常的注入来获取Bean并调用其方法:

@javax.ejb.EJB 
private Calculator calculator; 

...

float result = calculator.add( 10 , 10 ) ;
...

异步服务

处理可伸缩问题最简单的办法就是不去处理(直到容量不足时才考虑)!这种方式就是人尽皆知的SEDA(staged event driven architecture)模式,可以通过排队来避免瓶颈的出现。通过这种方式为任务排队,同时客户端可以继续后面的处理。如果组件的处理需要花费很长时间,同时系统没有过载,那么这种模式可以保证处理时间较长的组件不会把系统搞糟。

处理可伸缩性问题的另一个方法就是不要在单向消息交换时阻塞客户端的调用。此外还可以进行异步的处理,让客户端继续后面的处理直到结果返回。所有这些方法都加到了EJB 3.1中新的异步服务支持当中。我们可以在Bean或是单个方法上使用注解@javax.ejb.Asynchronous以告诉容器在调用结果返回前不要阻塞客户端的执行,这样客户端就可以继续后续的处理,从理论上来说,这种方式可以让容器缓存待处理的任务直到其可以处理为止。

如果使用@Asynchronous来注解Bean类或是业务接口,那么该注解会应用到Bean上的所有方法;否则只有使用了@Asynchronous注解的方法才会变成异步的。异步方法可以返回void或是java.util.concurrent.Future<V>的实例。客户端可以通过Future<V>实例来查询返回的结果,但方法调用后客户端可以继续后续的处理而不会被阻塞。在这种方式下,即便EJB花费了1、2个小时的处理时间也无所谓,因为客户端并不会受到任何影响。从概念上来看,这与向JMS队列发送请求来调用服务的方式是一致的。

Future<V>实例可用于取消任务或是等待结果。客户端的事务上下文不会被传播到异步方法中,这样对于异步方法来说,REQUIRED要比REQUIRES_NEW更具效率。

看个具体的示例吧:要构建的服务需要与几个Web Services通信并综合使用调用的结果。我们需要结果,但却不能挂起客户端的请求(或许是个网页)。如下代码展示了该服务:p>

@javax.ejb.Stateless
public CarHotelAndAirLineBookingServiceBean implements CarHotelAndAirLineBookingService  {
  @javax.ejb.Asynchronous  
  public Future<BookingConfirmation> bookCarHotelAndAirLine( Car rental, Hotel hotel, AirLine airLine) { 
    /**  ...  */

   } 
 } 

我们在客户端(一个JSF action)这样调用服务:

@Named 
public BookingAction { 

        @javax.ejb.EJB  private  CarHotelAndAirLineBookingServiceBean bookingService; 

        private Future<BookingConfirmation> confirmation;

        public String makeRequest(){ 

                Hotel hotelSelection = ... ;
                Car rentalSelection = ... ;

                AirLine airLineSelection = ... ; 

                confirmation = bookingService.bookCarHotelAndAirLine(
                    rentalSelection, hotelSelection, airLineSelection ) ; 
                return "showConfirmationPending";
        }

        public Future<BookingConfirmation> getConfirmation(){ /* ... */ }

        /* ... */
}

简化的部署

EJB 3.1还首次提供了动态、更加简化的部署方式,支持.WAR文件内的部署。在打包到WEB-INF/classes或是WEB-INF/classes下的.jar文件中时,具有组件定义(component-defining)注解的类将成为Enterprise Bean组件。此外,还可以通过WEB-INF/ejb-jar.xml文件来定义Enterprise Bean。打包到.WAR中的Bean共享单独的命名空间并成为.WAR环境的一部分。这样,将.jar打包到WEB-INF/lib下就与将class文件放到WEB-INF/classes中是一样的了。

新规范另一个值得注意的特性就是EJB Lite。对于很多应用来说,EJB技术显得过于庞大了。EJB Lite提供了EJB的一套子集,关注于Session Bean的使用。它提供了一种方式以嵌入式风格来使用EJB组件,这简化了单元测试。EJB Lite支持无状态、有状态以及单例的Session Bean。Bean可以有Local接口,也可以没有接口。他们可以与拦截器协同工作并使用事务和安全等容器服务。

EJB 3.1是个强大的开发者工具集,能满足应用80%的需要。规范的未来是光明的,同时也首次明确地提及了未来Java SE的裁剪机制将要移除的特性。未来可能会移除的特性包括老式的容器管理和Bean管理的持久化、Entity Bean的EJB 2.1客户端视图、EJB QL(EJB 2.1的查询语言)以及基于JAX-RPC的Web Service支持(包括J2EE 1.4引入的端点和客户端视图)。显然,EJB 3.1是个引人注目、向后兼容的升级,它代表了5年前开始的JSR 220(EJB 3.0)的新进展。

查看英文原文:Java EE6: EJB3.1 Is a Compelling Evolution

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

看看 by ren ping

了解了下

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

1 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT