当前页面: 开发资料首页 → JSP 专题 → EJB 3.0+Aspect实现声明性编程初步
摘要: EJB 3.0+Aspect实现声明性编程初步
  提要 本文将与你一同探讨怎样把注解和方面的威力联合起来,以与EJB 3.0兼容的方式为企业实现提供声明性服务,而在同时仍然提供容器的独立性。
  一、 引言
  在我们共同寻求进一步提高软件开发生产性能的方法的过程中,我们-作为Java社团成员-一般都转向J2EE来提供针对企业开发中更具挑战性的技术问题如分布式事务管理、并发性和对象分布等的解决方案。其背后的指导思想-这些复杂的企业服务能被应用程序服务器供应商所实现并能为商业开发者所平衡-的确是一种很好的思想。J2EE,具体地说是EJB,已成功地提供了一个平台-在其上构建企业Java应用程序。
  这其中部分的成功是由于能够进行声明性编程-一种程序开发方式-用这种方式,你可以声明基础结构服务而不是用商业逻辑明确地编码从而使代码散布于各处。EJB已经证明了这种编程方式的价值-通过允许企业问题例如事务和安全被用一种发布描述符所声明并为容器所处理。
  然而,在过去的岁月中,越来越多的开发者认识到EJB在团队的生产效率方面给它自己带来新的大量的挑战-每个EJB必须伴随多个接口,以一种发布描述符描述,经由JNDI被存取,等等。而在容器外EJB上进行单元测试也带来另外的困难,如今EJB已不再把重点放在单纯的面向对象开发上。
  请注意,为阅读本文您需具备如下工具: 
  ·Java 2 SDK 1.5
  ·Maven 2.0 Beta 2
  EJB 3.0的目标在于从以下几个方面使企业开发更为容易:
  ·通过引入元数据注解来实现声明性请求企业服务
  ·经由注解实现依赖性/资源注入
  ·实现企业beans与EJB特定接口的解耦
  ·经由轻量级的对象关系映射实现持续性存储的简化
  这对于EJB开发者来说尤如一股春风-一直以来,他们竭力地从事开发、测试和维护EJB。利用EJB 3.0写一个企业bean现在变得很容易,就如用特定的注解创建一个POJO(传统的Java对象)以把它标明为一个EJB并请求企业服务。下面是一个来自于EJB 3.0 Public Draft中EJB的例子:
@Stateful
public class CartBean implements ShoppingCart 
{
private float total;
private Vector productCodes;
public int someShoppingMethod(){...};
...
}
public class TravelAgencyServiceImpl implements ITravelAgencyService
{
 public IFlightDAO flightDAO;
 public TravelAgencyServiceImpl()
 { flightDAO = FlightDAOFactory.getInstance().getFlightDAO(); }
 public void bookTrip(long outboundFlightID, long returnFlightID, int seats)
 throws InsufficientSeatsException
 {
  reserveSeats(outboundFlightID, seats);
  reserveSeats(returnFlightID, seats);
 }
}
public class TravelAgencyServiceImpl implements ITravelAgencyService
{
 @Resource(name = "flightDAO")
 public IFlightDAO flightDAO;
 public void bookTrip(long outboundFlightID, long returnFlightID, int seats)
 throws InsufficientSeatsException
 {
  reserveSeats(outboundFlightID, seats);
  reserveSeats(returnFlightID, seats);
 }
}
@Aspect
public class InjectionAspect
{
 private DependencyManager manager = new DependencyManager();
 @Before("get(@Resource * *.*)")
 public void beforeFieldAccesses(JoinPoint thisJoinPoint)
 throws IllegalArgumentException, IllegalAccessException
 {
  FieldSignature signature = (FieldSignature) thisJoinPoint.getSignature();
  Resource injectAnnotation = signature.getField().getAnnotation(Resource.class);
  Object dependency = manager.resolveDependency(signature.getFieldType(),injectAnnotation.name());
  signature.getField().set(thisJoinPoint.getThis(), dependency);
 }
}
  这个简单方面所做的全部是,从一个属性文件(这个逻辑被封装在DependencyManager对象中)查询实现类并且在存取字段之前把它注入到用@Resource注解所注解的字段中。显然,这种实现不是完整的,但是它确实说明了你可以怎样以一种JSR 250兼容方式且不需采用EJB来提供资源注入。
  四、 安全性
  除了资源注入外,JSR 250和EJB 3.0还提供经由注解的元数据安全表示。javax.annotation.security包定义了五个注解-RunAs,RolesAllowed,PermitAll,DenyAll和RolesReferenced-所有这些都能应用到方法上来定义安全要求。例如,如果你想要声明上面列出的bookFlight方法仅能为具有"user"角色的调用者所执行,那么你可以用如下的安全约束来注解这个方法:
public class TravelAgencyServiceImpl implements ITravelAgencyService
{
 @Resource(name = "flightDAO")
 public IFlightDAO flightDAO;
 @RolesAllowed("user")
 public void bookTrip(long outboundFlightID, long returnFlightID, int seats)
 throws InsufficientSeatsException
 {
  reserveSeats(outboundFlightID, seats);
  reserveSeats(returnFlightID, seats);
 }
}
@Aspect
public class SecurityAspect
{
 @Around("execution(@javax.annotation.security.RolesAllowed * *.*(..))")
 public Object aroundSecuredMethods(ProceedingJoinPoint thisJoinPoint) 
 throws Throwable
 {
  boolean callerAuthorized = false;
  RolesAllowed rolesAllowed = rolesAllowedForJoinPoint(thisJoinPoint);
  for (String role : rolesAllowed.value())
  {
   if (callerInRole(role))
   { callerAuthorized = true; }
  }
  if (callerAuthorized)
  { return thisJoinPoint.proceed(); }
  else
  {
   throw new RuntimeException("Caller not authorized to perform specified function");
  }
 }
 private RolesAllowed rolesAllowedForJoinPoint(ProceedingJoinPoint thisJoinPoint)
 {
  MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
  Method targetMethod = methodSignature.getMethod();
  return targetMethod.getAnnotation(RolesAllowed.class);
 }
 private boolean callerInRole(String role)
 { ... }
}
  五、 事务
  事务成为企业开发的一个重要部分-因为它们有助于在一个并发的环境中的数据集成。从一个高层次上看,事务可以通过多种或者是完整的或者是都不完整的操作来保证这一点。
  不象针对资源注入和安全的注解,针对事务的注解是特定于EJB 3.0的并且没有在JSR 250普通注解中定义。EJB 3.0定义了两个与事务相联系的注解:TransactionManagement和TransactionAttribute。该TransactionManager注解指定事务是由容器所管理还是为bean所管理的。在EJB 3中,如果这个注解没被指定,那么将使用容器所管理的事务。TransactionAttribute注解用于指定方法的事务传播级别。有效值-包括强制的、要求的、要求新的、支持的、不支持的和从不支持的-用来定义是否要求一个已有事务或启动一个新的事务,等等。
  因为bookFlight操作包含两步-订购一个外出航班和一个返回航班,所以,通过把它包装成一个事务,你能保证这项操作的一致性。通过使用EJB 3.0事务注解,这将看上去如下所示:
public class TravelAgencyServiceImpl implements ITravelAgencyService
{
 @Resource(name = "flightDAO")
 public IFlightDAO flightDAO;
 @RolesAllowed("user")
 @TransactionAttribute(TransactionAttributeType.REQUIRED)
 public void bookTrip(long outboundFlightID, long returnFlightID, int seats)
 throws InsufficientSeatsException
 {
  reserveSeats(outboundFlightID, seats);
  reserveSeats(returnFlightID, seats);
 }
}
@Aspect
public class TransactionAspect
{
 @Pointcut("execution(@javax.ejb.TransactionAttribute * *.*(..))")
 public void transactionalMethods(){}
 @Before("transactionalMethods()")
 public void beforeTransactionalMethods()
 { HibernateUtil.beginTransaction(); }
 @AfterReturning("transactionalMethods()")
 public void afterReturningTransactionalMethods()
 { HibernateUtil.commitTransaction(); }
 @AfterThrowing("transactionalMethods()")
 public void afterThrowingTransactionalMethods()
 { HibernateUtil.rollbackTransaction(); }
}
@Stateful
public class TravelAgencyServiceImpl implements ITravelAgencyService
{ ... }