SeamFramework.orgCommunity Documentation

Chapter 10. Decorators

10.1. Delegate object
10.2. Enabling decorators

Interceptors are a powerful way to capture and separate concerns which are orthogonal to the application (and type system). Any interceptor is able to intercept invocations of any Java type. This makes them perfect for solving technical concerns such as transaction management, security and call logging. However, by nature, interceptors are unaware of the actual semantics of the events they intercept. Thus, interceptors aren't an appropriate tool for separating business-related concerns.

The reverse is true of decorators. A decorator intercepts invocations only for a certain Java interface, and is therefore aware of all the semantics attached to that interface. Since decorators directly implement operations with business semantics, it makes them the perfect tool for modeling some kinds of business concerns. It also means that a decorator doesn't have the generality of an interceptor. Decorators aren't able to solve technical concerns that cut across many disparate types. Interceptors and decorators, though similar in many ways, are complementary. Let's look at some cases where decorators fit the bill.

Suppose we have an interface that represents accounts:

public interface Account {

   public BigDecimal getBalance();
   public User getOwner();
   public void withdraw(BigDecimal amount);
   public void deposit(BigDecimal amount);
}

Several different beans in our system implement the Account interface. However, we have a common legal requirement that; for any kind of account, large transactions must be recorded by the system in a special log. This is a perfect job for a decorator.

A decorator is a bean (possibly even an abstract class) that implements the type it decorates and is annotated @Decorator.

@Decorator

public abstract class LargeTransactionDecorator
      implements Account {
   ...
}

The decorator implements the methods of the decorated type that it wants to intercept.

@Decorator

public abstract class LargeTransactionDecorator
      implements Account {
   @Inject @Delegate @Any Account account;
   @PersistenceContext EntityManager em;
    
   public void withdraw(BigDecimal amount) {
      ...
   }
    
   public void deposit(BigDecimal amount);
      ...
   }
}

Unlike other beans, a decorator may be an abstract class. Therefore, if there's nothing special the decorator needs to do for a particular method of the decorated interface, you don't need to implement that method.

Interceptors for a method are called before decorators that apply to the method.

Decorators have a special injection point, called the delegate injection point, with the same type as the beans they decorate, and the annotation @Delegate. There must be exactly one delegate injection point, which can be a constructor parameter, initializer method parameter or injected field.

@Decorator

public abstract class LargeTransactionDecorator
      implements Account {
   @Inject @Delegate @Any Account account;
   ...
}

A decorator is bound to any bean which:

This delegate injection point specifies that the decorator is bound to all beans that implement Account:

@Inject @Delegate @Any Account account;

A delegate injection point may specify any number of qualifier annotations. The decorator will only be bound to beans with the same qualifiers.

@Inject @Delegate @Foreign Account account;

The decorator may invoke the delegate object, which has much the same effect as calling InvocationContext.proceed() from an interceptor. The main difference is that the decorator can invoke any business method on the delegate object.

@Decorator

public abstract class LargeTransactionDecorator
      implements Account {
   @Inject @Delegate @Any Account account;
   @PersistenceContext EntityManager em;
    
   public void withdraw(BigDecimal amount) {
      account.withdraw(amount);
      if ( amount.compareTo(LARGE_AMOUNT)>0 ) {
         em.persist( new LoggedWithdrawl(amount) );
      }
   }
    
   public void deposit(BigDecimal amount);
      account.deposit(amount);
      if ( amount.compareTo(LARGE_AMOUNT)>0 ) {
         em.persist( new LoggedDeposit(amount) );
      }
   }
}

By default, all decorators are disabled. We need to enable our decorator. We can do it using beans.xml descriptor of a bean archive. However, this activation only applies to the beans in that archive. From CDI 1.1 onwards the decorator can be enabled for the whole application using @Priority annotation.


<beans
   xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
      http://java.sun.com/xml/ns/javaee
      http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
   <decorators>
         <class>org.mycompany.myapp.LargeTransactionDecorator</class>
   </decorators>
</beans>

This declaration serves the same purpose for decorators that the <interceptors> declaration serves for interceptors:

Decorators enabled using @Priority are called before decorators enabled using beans.xml, the lower priority values are called first.