Interceptors and Decorators - JBoss Weld CDI for Java Platform (2013)

JBoss Weld CDI for Java Platform (2013)

Chapter 6. Interceptors and Decorators

This chapter will cover how interceptors and decorators can be utilized within our CDI applications, while doing so in a typesafe manner through annotations. We will examine how interceptor bindings are created through annotations and then how to create and enable an interceptor. In decorators, we talk about what a delegate is before enabling decorators in a manner similar to interceptors.

Interceptors and decorators behave as two different sides of the same coin. Interceptors are perfect for separating concerns that are orthogonal to our application purpose by cutting across all layers of our application to perform an identical task. These tasks usually relate to technical matters such as transaction management, security, and method logging.

As interceptors are not aware of the semantics of what they intercept, they are not suited for separating business concerns, but that's where decorators come into play. As decorators intercept methods on a specific interface, they know everything about the contextin which that interface is used. Decorators are well suited for solving business problems that apply to a hierarchy of classes of the same type. In most cases, it would be possible to implement the same functionality as a decorator within an abstract parent of these classes, but then we lose the ability to disable and enable that functionality as required.

Interceptor bindings

If we want to provide auditing in our application, such as a sequence of particular events that occur, then we can use interceptors to help us. First off, we need to define an interceptor binding type that we can use to inform Weld about which beans we want to audit:

@InterceptorBinding

@Target( { METHOD, TYPE } )

@Retention( RUNTIME )

public @interface Audited {}

With the interceptor binding type created, we can set our AccountManagement to be an audited bean:

@Audited

public class AccountManagement { ... }

Though, if we only want a specific method to be audited, we can specify:

public class AccountManagement {

@Audited

public void createAccount() { ... }

}

Creating and enabling an interceptor

Now that we have the interceptor binding type, we need to implement the interceptor. All that's required is to create a standard interceptor and annotate it with @Interceptor and our interceptor binding type of @Audited:

@Audited

@Interceptor

public class AuditInterceptor {

@AroundInvoke

public Object auditMethod(InvocationContext ctx)

throws Exception

{ ... }

}

CDI interceptors are also able to take full advantage of dependency injection like all beans in our application:

@Audited

@Interceptor

public class AuditInterceptor {

@Inject

AuditService service;

@AroundInvoke

public Object auditMethod(InvocationContext ctx)

throws Exception

{ ... }

}

Now we're ready to enable our interceptor for our application. As all interceptors are disabled by default, we need to enable our interceptor within the beans.xml file of a bean archive:

<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">

<interceptors>

<class>org.cdibook.chapter6.AuditInterceptor</class>

</interceptors>

</beans>

Note

Activation of an interceptor within beans.xml will activate it for all beans within the same archive only.

It might seem strange to be using XML with a framework that espouses annotations over an XML configuration, but it does provide the following advantages that wouldn't be possible otherwise:

· We can specify an absolute ordering of all interceptors within our application to ensure deterministic behavior and startup.

· Our interceptor can be enabled or disabled during deployment. So we can only enable our auditing interceptor in production and not in development and test environments.

Note

The interceptor's ordering is determined by the order of the <class> elements within beans.xml.

Advanced interceptors

These next sections will delve deeper into interceptor binding types to explain more advanced use cases of them. We will cover binding types with members, how to combine different interceptors for a single use, and how binding types can be inherited.

Interceptor binding types with members

Just as with qualifier annotations, we can also add members to interceptor binding types too, such as:

@InterceptorBinding

@Target( { METHOD, TYPE } )

@Retention( RUNTIME )

public @interface Audited {

boolean logToFile() default false;

}

With the binding type we just created, we need to create a new interceptor that matches the situation of logToFile being true, as CDI uses the member as a means for choosing a different interceptor implementation. For our preceding interceptor binding type, the implementation would be:

@Audited( logToFile = true )

@Interceptor

public class AuditFileInterceptor {

@AroundInvoke

public Object auditMethod(InvocationContext ctx)

throws Exception

{ ... }

}

And we would use the interceptor with a member:

@Audited( logToFile = true )

public class AccountManagement { ... }

What if we don't necessarily need to create two separate interceptors, and we're using logToFile as a way to pass information into our interceptor and not to provide a completely separate interceptor implementation? In this situation, we can retain the original interceptor implementation and use the @Nonbinding annotation on the member of the binding type:

@InterceptorBinding

@Target( { METHOD, TYPE } )

@Retention( RUNTIME )

public @interface Audited {

@Nonbinding

boolean logToFile() default false;

}

Combining interceptor binding types

Binding two separate interceptors to a bean is only a matter of adding their binding type annotations to the bean itself. However, we may encounter a complicated use case that requires a separate interceptor to handle the combination of the two binding types. For this situation, the interceptor itself can specify multiple bindings:

@Audited

@Loggable

@Interceptor

public class AuditLoggingInterceptor { ... }

We can bind our interceptor to the createAccount() method with any of these combinations:

public class AccountManagement {

@Audited

@Loggable

public void createAccount() { ... }

}

Or:

@Audited

@Loggable

public class AccountManagement {

public void createAccount() { ... }

}

Or:

@Audited

public class AccountManagement {

@Loggable

public void createAccount() { ... }

}

This previous example could also have the annotations reversed between the type and method, as both will achieve the same result.

Inheritance of interceptor binding types

We are able to implement inheritance within interceptor binding types as their definition is transitive.

Note

An interceptor binding type that is annotated with another interceptor binding type allows the former binding type to inherit the latter.

@Loggable

@Audited

@InterceptorBinding

@Target( { METHOD, TYPE } )

@Retention( RUNTIME )

public @interface AuditLogged {}

This removes the need to use both @Loggable and @Audited on a bean type or method. To ensure that both interceptors are called, we can simply annotate it with @AuditLogged to achieve the same result.

Note

If our AuditLoggingInterceptor exists within the system and is enabled, it would also be called along with the interceptors for @Loggable and @Audited.

What is a decorator delegate?

Say our application has beans that implement the following:

public interface Account {

Account create();

String getId();

void changePassword(String password);

}

We want to monitor how frequently the password on an account is changed to track potential hacking in our system. A decorator is an ideal way to solve the problem.

Our decorator is a bean, which can be abstract, that implements the required type and is annotated with @Decorator:

@Decorator

public abstract class PasswordMonitorDecorator implements Account {

@Inject

@Delegate

@Any

Account account;

@PersistenceContext

EntityManager em;

public void changePassword(String password) {

account.changePassword(password);

em.persist(new PasswordChange(account.getId()));

}

}

As the decorator can be abstract, we only need to implement the methods of the type being decorated if our decorator needs to perform any special operations when that method is called.

Note

If a method has both interceptors and decorators, interceptors are called first.

In our decorator, the account field is known as the delegate injection point of the decorator. As we specified @Any, the decorator will be bound to all beans that implement Account. If we didn't need or want all of the beans that implement Account to be bound to the decorator, we can utilize qualifiers on the delegate injection point to restrict the set of beans that match.

Note

A decorator may call any method that is present on the delegate object, not just the method being decorated.

Enabling a decorator

As with interceptors, all decorators are disabled by default, so we need to enable our decorator within the beans.xml file of a bean archive:

<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.cdibook.chapter6.PasswordMonitorDecorator</class>

</decorators>

</beans>

Note

Activation of a decorator within beans.xml will activate it for all beans within the same archive only.

Summary

In this chapter, we introduced the use of interceptors and decorators in CDI, including how they are enabled within beans.xml of a bean archive.

For interceptors, we explained what a binding type was and how it supported inheritance. We also covered how to use binding and @Nonbinding members of an interceptor binding type, and how different interceptor binding types can be combined on bean types and methods.

We saw a situation in which a decorator is suited to solve a business problem, how to specify that a class implementing a bean type is a decorator, and what delegate object is injected into the decorator.