/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.