devxlogo

Try Declarative Programming with Annotations and Aspects

Try Declarative Programming with Annotations and Aspects

n our shared quest for more highly productive methods of developing software, those of us in the Java community have looked to J2EE to provide solutions for the more challenging technical problems of enterprise development such as distributed transaction management, concurrency, and object distribution. The idea behind this?that these complicated enterprise services could be implemented by application server vendors and leveraged by business developers?is indeed a very good idea. J2EE, and EJB specifically, has successfully provided a platform on which to build enterprise Java applications.

Part of this success results from the enablement of declarative programming, a style of development in which infrastructure services are declared rather than being explicitly coded and interspersed with business logic. EJB has proven the value of this style of programming by allowing enterprise concerns, such as transactions and security, to be declared in a deployment descriptor and handled by the container.

Within the last couple of years, though, more and more developers have come to recognize that EJB brings its own set of challenges to a team’s productivity. Each EJB must be accompanied by multiple interfaces, described in a deployment descriptor, accessed via JNDI, etc. Performing unit tests on EJBs outside of a container presents additional difficulties, and EJBs shift the emphasis away from purer forms of object-oriented development. For more information on the debate surrounding EJB consult the related resources sidebar (see left column).

What You Need

EJB 3.0 aims to ease enterprise development in the following ways:

  • Introduction of metadata annotations to declaratively request enterprise services
  • Dependency/resource injection via annotations
  • Decoupling enterprise beans from EJB-specific interfaces
  • Simplified persistence via light-weight object-relational mapping

This comes as a breath of fresh air to EJB developers who have wrestled with developing, testing, and maintaining EJBs. With EJB 3.0 writing an enterprise bean will now be as simple as writing a POJO (Plain Old Java Object) with special annotations to identify it as an EJB and to request enterprise services. An example of an EJB from the EJB 3.0 Public Draft is shown below:

@Statefulpublic class CartBean implements ShoppingCart {    private float total;    private Vector productCodes;    public int someShoppingMethod(){...};    ...}

The EJB 3.0 specification essentially recognizes that what developers need is not a heavy-weight, one-size-fits-all solution, but rather a light-weight, easy-to-use solution that provides a range of enterprise services to the developer. One of the most important ways that EJB 3.0 meets this need is through the decoupling of enterprise beans from the EJB API. And this solution has the interesting ramification that EJBs can now not only be run across different EJB containers, but in fact they can be run within any application framework that recognizes the EJB 3.0 (JSR 220) and common annotations (JSR 250) used to declare enterprise services.

This article does not provide an in-depth exploration of declarative programming, EJBs, aspects, or annotations. Instead it takes a looks at the inter-relatedness of these technologies and how to combine them in novel ways to simplify application development.

In this article you will see how to write an EJB 3.0 compatible bean and provision it with declarative transaction management, security, and resource injection by writing a few simple aspects. I hope that you will gain several benefits from this exercise:

  • Learn three practical uses of aspects (dependency injection, security, and transaction).
  • Become familiar with EJB 3.0 and the ideas behind it.
  • Recognize how the decoupling of EJB from a specific API allows EJB 3.0 compliant services to be provided by light-weight implementations, and not just to EJBs.

Example Application?Flight Booking
Through the rest of this discussion you will examine an implementation of a flight booking system that uses aspects with annotations to implement dependency injection, security, and transaction management. The application performs only two functions: It allows users to search for flights (Figure 1) and then to book a trip (Figure 2). Both of these operations will be secured to allow only recognized users to perform them. Also, since the ‘book trip’ operation involves booking two flights (the outbound and return flights), this operation will need to be transactional; i.e., both bookings will either succeed or fail as a unit of work.


Figure 1. Flight Search: First, users search for flights that meet their specified criteria.
 
Figure 2. Flight Booking: Next, users book both an outbound and a return flight. Both bookings must succeed or both must fail.

This simple Web application consists of a couple of servlets, a service façade, and a DAO layer (see Figure 3).
Figure 3. Flight Booking System Architecture: The flight booking system includes three main types of components which collaborate to fulfill the user’s request.

The cross-cutting concerns of resource configuration, security, and transaction management will be provisioned by aspects (implemented with AspectJ 1.5 M3) which inject behavior declared in Java 5 annotations. The sample code can be downloaded from the sidebar of the article (see left column) and built using Maven 2.0.

Resource Injection
This EJB 3.0 draft specification allows resources to be declared via the @Resource annotation, which is defined in the draft common annotations specification, and injected into your EJB by the container. Dependency injection is a technique by which an object’s dependencies are provided (injected) by an entity external to an object rather than explicitly created by the object. It is sometimes described as the Hollywood Principle, which jokingly means “don’t call us, we’ll call you.”

Take the example of the TravelAgencyServiceImpl class which needs to find an implementation of the IFlightDAO interface in order to persist some data. Traditionally this is accomplished via a factory, singleton, service locator, or some other custom solution. One solution might look like the following:

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);    }}

As you can see, this implementation involves the creation of a special factory class which most likely reads configuration information stored somewhere to know what implementation of IFlightDAO to create. If instead of having the service explicitly create its dependencies they are injected by the container, then the details of configuration and object creation are delegated to the container. This allows the components of an application to be easily “wired” together in different configurations and eliminates a lot of rote singleton and factory code.

An implementation of this class that has its dependency on an implementation of IFlightDAO declared with a JSR 250 resource annotation might look as follows:

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);    }}

In this case the container will provide the service class with the correct implementation of a resource named “flightDAO.” But what if you want to take advantage of resource injection now without waiting for the EJB 3.0 release? Well, you can adopt a light-weight container that provides dependency injection such as Spring or Pico Container. However, I am not currently aware of a lightweight container that uses JSR 250 resource annotations to specify injection requirements (although I would expect to see some emerge in this space).

One solution is to use aspects to implement dependency injection. If you use the @Resource annotation for this purpose then your implementation will be consistent with the EJB 3.0 approach and forward-compatible with EJB 3.0 implementations?and this is not very difficult to do. The listing below shows an aspect created with AspectJ that injects field annotated with the @Resource annotation:

@Aspectpublic 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);    }}

All this simple aspect does is look up the implementation class from a property file (this logic is encapsulated in the DependencyManager object) and inject it into fields annotated with the @Resource annotation before field accesses. Voila: dependency injection a la JSR 250 and EJB 3.0! Obviously this implementation is not complete, but it does illustrate how one could go about providing resource injection in a JSR 250-compatible manner without necessarily adopting EJB.

Security
In addition to resource injection, JSR 250 and EJB 3.0 also provide for the expression of security metadata via annotations. The javax.annotation.security package defines five annotations?RunAs, RolesAllowed, PermitAll, DenyAll, and RolesReferenced?all of which can be applied to methods to define security requirements. For example, if you wanted to declare that the bookFlight method listed above could be executed only by callers with the role “user,” then you could annotate that method with that security restriction as follows:

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);    }}

This annotation would signal that the container is responsible for ensuring that only callers with the specified role can execute the method. So now I will show another simple aspect that will enforce this security constraint on the application:

@Aspectpublic 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)    {        ...    }}

This aspect surrounds all executions of methods annotated with the @RolesAllowed annotation and ensures that the caller is authorizes to invoke the method by verifying that the caller is in one of the roles specified in the annotation. You can of course substitute any algorithm you would like to authorize the user and retrieve his or her roles such as JAAS or a custom solution. In the example code I decided to delegate to the servlet container for convenience.

Transactions
Transactions are an important part of enterprise development since they help ensure integrity of data in a concurrent environment. At a high level, transactions ensure that with multiple operations either all complete or that none complete at all. For more information on transactions see my previous article, “Using Annotations with Aspects in Pre-Java 5 Versions,” or the related resources sidebar.

Unlike the annotations for resource injection and security, the annotations for transactions are specific to EJB 3.0 and not defined in JSR 250 common annotations. EJB 3.0 defined two annotations related to transactions: TransactionManagement and TransactionAttribute. The TransactionManager annotation specifies whether transactions are to be container-managed or bean-managed. In EJB 3, if this annotation is not specified then container-managed transactions are assumed. The TransactionAttribute annotation specifies transaction propagation level of the method. Valid values?including mandatory, required, requires new, supports, not supported, and never?define whether an existing transaction is required, or a new transaction should be started, etc.

Because the bookFlight operation involves two steps, booking an outbound and a returning flight, you can ensure consistency of this operation by wrapping it in a transaction. Using the EJB 3.0 transaction annotations this would look as follows:

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);    }}

And you could apply a simple aspect that demarcates the transactions boundaries automatically:

@Aspectpublic 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();    }}

This implementation is based on the assumption that Hibernate and the ubiquitous thread local pattern are being used to manage the Hibernate Session and Transaction objects, but any appropriate implementation, such as one based on JTA, could be used instead.

This article has shown how cross-cutting concerns such as resource management, security, and transactions can be implemented as aspects using the EJB 3.0 and JSR 250 annotations sets. There are several lessons to be learned from this. This first is of course the blueprint that these sample aspects provides for modularizing cross-cutting concerns using AspectJ’s implementation. Secondly, we have looked at some of the news ideas and concepts behind the emerging EJB 3.0 specification. And lastly, we have seen in a dramatic fashion the freedom that decoupling our business objects from the EJB API has to offer. At this point all you have to do to make the TravelAgencyServiceImpl a stateless session bean is to add one last annotation:

@Statefulpublic class TravelAgencyServiceImpl implements ITravelAgencyService{    ...}

I hope this very liberating approach to the provisioning of enterprise services will spark much competition and innovation in the framework/container industry.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist