/This is the fifth installment of a series of articles describing the current status of the Web Beans specification. Please read the first, second, third and fourth installments first./

One of the themes of Web Beans is loose coupling. We've already seen three means of achieving loose coupling:

  • component types enable /deployment time/ polymorphism
  • producer methods enable /runtime/ polymorphism
  • contextual lifecycle management decouples component lifecycles

These techniques serve to enable loose coupling of client and server. The client is no longer tightly bound to an implementation of an API, nor is it required to manage the lifecycle of the server object. This approach lets /stateful objects interact as if they were services/.

Web Beans provides two extra important facilities that further the goal of loose coupling:

  • interceptors decouple technical concerns from business logic
  • event notifications decouple event producers from event consumers

Let's explore these features.

Interceptors

Web Beans re-uses the basic interceptor architecture of EJB 3.0, extending the functionality in two directions:

  • any Web Bean may have interceptors, not just session beans
  • Web Beans features a more sophisticated annotation-based approach to binding interceptors to components

Suppose we want to declare that some of our components are transactional. The first thing we need is an /interceptor binding annotation/ to specify exactly which components we're interested in:

@InterceptorBindingType
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Transactional {}

Now we can easily specify that our ShoppingCart is a transactional object:

@Transactional @Component
public class ShoppingCart { ... }

Or, if we prefer, we can specify that just one method is transactional:

@Component
public class ShoppingCart {
    @Transactional public void checkout() { ... }
}

That's great, but somewhere along the line we're going to have to actually implement the interceptor that provides this transaction management aspect. All we need to do is create a standard EJB interceptor, and annotate it @Interceptor and @Transactional.

@Transactional @Interceptor
public class TransactionInterceptor {
    @AroundInvoke public Object manageTransaction(InvocationContext ctx) { ... }
}

Finally, we need to /enable/ our interceptor in web-beans.xml.

<interceptors>
    <interceptor>to.relation.in.TransactionInterceptor</interceptor>
</interceptors>

Whoah! Why the angle bracket stew?

Well, the XML declaration solves two problems:

  • it enables us to specify a total ordering for all the interceptors in our system, ensuring deterministic behavior
  • it lets us enable or disable interceptor classes at deployment time

For example, we could specify that our security interceptor runs before our TransactionInterceptor. And we can turn them both off in our test environment.

Interceptor bindings with members

Suppose we want to add some extra information to our @Transactional annotation:

@InterceptorBindingType
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Transactional {
    boolean requiresNew() default false;
}

Web Beans will use the value of requiresNew to choose between two different interceptors, TransactionInterceptor and RequiresNewTransactionInterceptor.

@Transactional(requiresNew=true) @Interceptor
public class RequiresNewTransactionInterceptor {
    @AroundInvoke public Object manageTransaction(InvocationContext ctx) { ... }
}

Now we can use RequiresNewTransactionInterceptor like this:

@Transactional(requiresNew=true) @Component
public class ShoppingCart { ... }

But what if we only have one interceptor and we want the container to ignore the value of requiresNew when binding interceptors? We can use the @NonBinding annotation:

@InterceptorBindingType
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Secure {
    @NonBinding String[] rolesAllowed() default {};
}

Multiple interceptor binding annotations

Usually we use combinations of interceptor bindings types to bind multiple interceptors to a component. For example, the following declaration would be used to bind TransactionInterceptor and SecurityInterceptor to the same component:

@Secure(rolesAllowed="admin") @Transactional @Component
public class ShoppingCart { ... }

However, in very complex cases, an interceptor itself may specify some combination of interceptor binding types:

@Transactional @Secure @Interceptor
public class TransactionalSecureInterceptor { ... }

Then this interceptor could be bound to the checkout() method using any one of the following combinations:

@Component
public class ShoppingCart {
    @Transactional @Secure public void checkout() { ... }
}
@Secure @Component
public class ShoppingCart {
    @Transactional public void checkout() { ... }
}
@Transactionl @Component
public class ShoppingCart {
    @Secure public void checkout() { ... }
}
@Transactional @Secure @Component
public class ShoppingCart {
    public void checkout() { ... }
}

Binding an interceptor to /everything/

What if we want an interceptor for /every/ component? Easy, just declare the interceptor without any interceptor binding type!

@Interceptor
public class UberInterceptor { ... }

Interceptor binding type inheritance

One of the awful, embarrassing, mistakes of the Java language is the lack of support for annotation inheritance. Really, annotations should have reuse built in, to allow this kind of thing to work:

public @interface Action extends Transactional, Secure { ... }

Well, fortunately, Web Beans works around this missing feature of Java:

@Transactional @Secure
@InterceptorBindingType
@Target(TYPE)
@Retention(RUNTIME)
public @interface Action { ... }

And now any component annotated @Action will be bound to both TransactionInterceptor and SecurityInterceptor. (And even TransactionalSecureInterceptor, if it exists.)

Events

/Please note that the following section describes functionality that is still under active discussion in the Web Beans expert group!/

The Web Beans event notification facility allows components to interact in a totally decoupled manner. Event /producers/ raise events that are then delivered to event /observers/. This basic schema might sound similar to the observer/observable pattern, but there are a couple of twists:

  • not only are event producers decoupled from observers; observers are completely decoupled from producers
  • observers can specify a combination of selectors to narrow the set of event notifications they will receive
  • observers can be notified immediately, or can specify that delivery of the event should be delayed until the end of the current transaction

Event observers

An /observer method/ is a method of any Web Bean with a parameter annotated @Observes.

public void onAnyDocumentEvent(@Observes Document document) { ... }

The annotated parameter is called the /event parameter/. Observer methods may also specify selectors, which are called /event binding types/. An event binding type is just an annotation:

@EventBindingType
@Target({PARAMETER, FIELD})
@Retention(RUNTIME)
public @interface Updated { ... }

We specify the selector by annotating the event parameter:

public void afterDocumentUpdate(@Observes @Updated Document document) { ... }

The observer method may have additional parameters, which are injected, according to the usual Web Beans semantics:

public void afterDocumentUpdate(@Observes @Updated Document document, @Current User user) { ... }

Event producers

The event producer may obtain an /event notifier/ by injection:

@In @Notifier Event<Document> documentEvent;

The @Notifier annotation implicitly defines a Web Beans component with scope @Dependent and component type @Standard.

A producer raises events by calling the one and only method of the Event interface:

documentEvent.raise(document);

To specify a selector, the producer may either pass an instance of the event binding type to the raise():

documentEvent.raise(document, new Updated(){});

Of may specify the selector at the injection point:

@In @Notifier @Updated Event<Document> documentUpdatedEvent;

Event bindings with members

An event binding type may have annotation members:

@EventBindingType
@Target({PARAMETER, FIELD})
@Retention(RUNTIME)
public @interface Role {
    RoleType value();
}

The member value is used to narrow the messages delivered to the observer:

public void adminLoggedIn(@Observes @Role(ADMIN) LoggedIn event) { ... }

And can be specified by the event producer either statically:

@In @Notifier @Role(ADMIN) Event<LoggedIn> LoggedInEvent;

Or dynamically:

documentEvent.raise( document, new Role() { public void value() { return user.getRole(); } } );

Multiple event bindings

Event binding types can be combined, for example:

@In @Notifier @Blog Event<Document> blogEvent;
...
if (document.isBlog()) blogEvent.raise(document, new Updated(){});

In this case, any of the following observer methods would be notified:

public void afterBlogUpdate(@Observes @Updated @Blog Document document) { ... }
public void afterDocumentUpdate(@Observes @Updated Document document) { ... }
public void onAnyBlogEvent(@Observes @Bog Document document) { ... }
public void onAnyDocumentEvent(@Observes Document document) { ... }

Transactional observers

Transactional observers receive their event notifications during the before or after completion phase of the transaction in which the event was raised. For example, the following observer method needs to refresh a query result set that is cached in the application context, but only when transactions that update the Category tree succeed:

public void refreshCategoryTree(@AfterTransactionSuccess @Observes CategoryUpdateEvent event) { ... }

There are three kinds of transactional observers:

  • @AfterTransactionSuccess observers are called during the after completion phase of the transaction, but only if the transaction completes successfully
  • @AfterTransactionCompletion observers are called during the after completion phase of the transaction
  • @BeforeTransactionCompletion observers are called during the before completion phase of the transaction

Transactional observers are very important in a stateful component model, because state is often held for longer than a single atomic transaction.


Back to top