8
Aspect-Oriented Programming (Interceptors)

WHAT’S IN THIS CHAPTER?            

  • Introduction to aspect-oriented programming
  • Aspects in Java
  • Using servlet filters as aspects
  • Aspects in Java EE, interceptors
  • EJB interceptors versus CDI interceptors

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

The wrox.com code download for this chapter is found at www.wrox.com/go/projavaeedesignpatterns on the Download Code tab. The code is in the Chapter 8 download and individually named according to the names throughout the chapter.

Aspect-oriented programming (AOP) is not a new concept. Its place in Java and third-party frameworks was secured from the early days of enterprise development. Despite this, it was not one of the classical design patterns listed in the GOF1 book.

AOP introduced a new concept and paradigm to programming. The idea relies on basing the code execution order on aspects. Each aspect intercepts the program’s execution and adds its own behavior before continuing with the call.

Aspects act like magic, adding further logic and behavior to the code at run time. However, this also brings an ambiguous and hard-to-follow code execution order that can often result in almost undebuggable code. AOP has many followers and fans, besides many haters.

Luckily, Java EE has a nice and clean implementation that can be helpful if it’s used in the right way and context.

WHAT IS ASPECT-ORIENTED PROGRAMMING?

Aspect-oriented programming (AOP) aims to add behavior to existing code or applications to solve common concerns. It is fairly normal to receive a new logging or security request in the middle of the development cycle. Such requests may consume a huge amount of time in refactoring existing code even though the logging code is a bunch of repetitive lines. Such common concerns, whether they appear in the middle of the development cycle or in the design phase of the project, are called cross-cutting concerns and can be addressed with AOP.

AOP became a popular programming paradigm during the past decade. Although Java did not offer a full-fledged out-of-the-box solution, some well-implemented third-party frameworks offered AOP. AspectJ and Spring are widely accepted and have been used for a long time in Java-based projects. Java also had a similar but more basic approach with servlet filters, although it is limited to web requests. With servlet filters, any request or response can be intercepted, and any additional behavior can be added.

Java EE adopted AOP and introduced the interceptor concept. Each update to Java EE brought new functionalities and unleashed the full potential of AOP to the Java EE platform.

AOP is not classified as a design pattern but is accepted as a programming paradigm. Neither the GOF book nor Head First Design Patterns2 discusses aspects. However, if either one did, an appropriate description would be “Provides a way to change execution behavior at run time (or compile time) to address cross-cutting concerns in the existing code base.”

AOP relies on code injection during compile time or run time to add the desired behavior or functionality to each point of an existing code base that matches the given injection criteria. Frameworks that perform compile-time injection usually out-perform, but they produce class files that do not match the source code line by line because of the injected code. Runtime injection does not modify the source or class files and performs injection by intercepting calls and executing the desired code before or after the original execution order.

AOP can prove to be useful if it is necessary to add a repetitive action, such as logging or security, to a code base. The aspects can be turned on or off depending on the environment or phase of the project. Aspects can dynamically add the desired behavior to running code. They dynamically decorate the method calls just as the decorator pattern decorates objects.

AOP can be a great tool to encapsulate common nonbusiness concerns. However, AOP can also be confusing if it adds behavior to business logic. Such implementations cause decentralized, distributed, and hard-to-test-and-debug business logic. The resulting code would be hard to maintain.

IMPLEMENTING AOP IN PLAIN CODE

Java SE does not offer out-of-the-box support for AOP. You can achieve plain AOP by using third-party frameworks such as AspectJ or Spring. Such frameworks used to depend on XML-only configuration; however, you can now achieve AOP through the use of annotations. Implementation and configuration of both frameworks are beyond the scope of this book, but they have a proven record and can easily be implemented. They both provide a valid alternative to the Java EE implementation.

However, Java web applications have the advantage of using servlets to intercept the request or the response, which works similarly to aspects. To implement a servlet filter, create a new class file and implement the servlet filter interface. Then provide an implementation of the doFilter() method, as shown in Listing 8-1.

The web container needs the configuration shown in Listing 8-2 to activate the servlet filter on given uniform resource locators (URLs). This is placed in the web application’s web.xml file.

It is even easier to implement filters using Servlet 3.0 like in Listing 8-3 because it uses annotations and does not need XML configuration.

Servlet filters are easy-to-implement tools, but they’re also powerful. However, the functionality is still limited to client server web requests. To intercept other method calls or to fine-tune the interception, you need a much more sophisticated approach.

ASPECTS IN JAVA EE, INTERCEPTORS

J2EE did not offer an out-of-the-box AOP solution but worked in harmony with third-party frameworks. Java EE 5 introduced interceptors, which resulted in an easy-to-use built-in aspect approach. However, the interceptor concept was limited to Enterprise JavaBeans (EJB) until Context and Dependency Injection (CDI) was introduced.

Interceptors in Java EE work in a similar way to aspects. Each interceptor addresses the concern and hosts the code block that contains the functionality to be added. The target to be decorated is called an advice. Each call to an advice within the scope of the interceptor is intercepted. The exact location of the aspect to be executed is called the pointcut.

Basic Java EE interceptors can only work on EJBs. Imagine an application consisting of hundreds of EJBs. The whole application can be configured to log all EJB calls by deploying an interceptor targeting all those EJBs.

Implementing interceptors in Java EE is straightforward. The first step is to create a new interceptor class and annotate it with the @Interceptor annotation. This class hosts the advice code. Any method annotated with @AroundInvoke is executed at the pointcut. However, there are some syntax rules regarding the pointcut method signature:

  • Any pointcut method must return an object of type Object and have a parameter of type InvocationContext.
  • Throw an exception.

You can use the InvocationContext parameter to access information about the current context as seen in Listing 8-4.

To put the interceptor class into action, you must annotate the target advice with the 
@Interceptors annotation as in Listing 8-5. The @Interceptors annotation would only be used in an EJB or MDB (Message Driven Bean).

The Interceptors annotation is flexible. You can also use it at both the class and the method levels. The Interceptors annotation also supports multiple interceptor inputs, which enable multiple interceptors on the target advice. Listing 8-5 uses class-level interceptors, which means that the Security interceptor will intercept either of the service calls. If you do not want the interceptor to cover all method calls in the class, you can use method-level annotations, as shown in Listing 8-6.

This time only calls to the startService() method are intercepted, unlike in Listing 8-5, in which all methods of the class were intercepted. You should annotate each method separately.

Using @Interceptor, @Interceptors with @AroundInvoke unleashes a powerful tool that solves cross-cutting concerns in an AOP approach. Yet interceptors offer easy annotation-based implementation with no boilerplate code.

You can use the InvocationContext interface to extract information about the context or interact with the advice context. Following are some of the more useful methods:

METHOD DESCRIPTION
public Object getTarget(); Return to the target advice.
public Method getMethod(); Return the executed method from the advice.
public Object[] getParameters(); Access target advice method’s parameters.
public void setParameters(Object[]); Set target advice method’s parameters.
public java.util.Map<String,Object> getContextData(); Access context data.
public Object proceed() throws Exception; Continue execution.

In Listing 8-7, you can access the method name. Also, you can check whether the interceptor had authorized the access before; if it has not, you can authorize the user for that method.

Interceptor Life Cycle

You can capture the interceptor’s life-cycle phases easily with the help of life-cycle annotations. Unlike extending and overriding, using life-cycle annotations hooks any function to the appropriate phase. The available life-cycle annotations are @PostConstruct, @PrePassivate, @PostActivate, and @PreDestroy. Listing 8-8 shows how to hook up using interceptor life-cycle methods.

Because the hooks rely on annotations, method names do not make a difference; you can use any name.

Default-Level Interceptors

Marking the target advice with Interceptors annotation provides an easy implementation and setup, but the nature of AOP usually asks for more. Most scenarios require the interceptor to perform its operation targeting all advices. Imagine adding interceptors for logging or security—targeting only a subset of EJB wouldn’t work. Also, annotating each EJB can become cumbersome and can easily lead to human error.

Java EE offers default-level interceptors to target all or subsets of EJB matching the naming scheme provided. Unlike in the previous example, to implement default-level interceptors, you need XML configuration:

<ejb-jar...>
  <interceptors>
      <interceptor>
          <interceptor-class>
              com.devchronicles.SecurityInterceptor
          </interceptor-class>
      </interceptor>
  </interceptors>
  <assembly-descriptor>
      <interceptor-binding>
          <ejb-name>*</ejb-name>
          <interceptor-class>
              <interceptor-class>
                  com.devchronicles.SecurityInterceptor
              </interceptor-class>
          </interceptor-class>
      </interceptor-binding>
  </assembly-descriptor>
</ejb-jar>

The first part of the XML file lists the interceptors; then the interceptor bindings need to be declared. This is done in the assembly description part, which can accept a wildcard (*) that applies to all or a specific name to create the binding between the interceptors and the EJB. The order of the interceptors listed also determines the execution order. The interceptors listed in the EJB-JAR file apply only to EJB in the same module.

Interceptor Order

If more than one interceptor has been defined for an advice, the order of the execution will be from the most general to the most specific case. This means that default-level interceptors will be executed before class-level interceptors, which will be followed by method-level interceptors.

This behavior is expected; nevertheless, the order of same-level interceptors can be a bit more confusing. If there is more than one default-level interceptor, the order in the EJB-JAR file determines the order of the execution of the interceptors.

<ejb-jar...>
  <interceptors>
      <interceptor>
          <interceptor-class>
              com.devchronicles.SecurityInterceptor
          </interceptor-class>
          <interceptor-class>
              com.devchronicles.AnotherInterceptor
          </interceptor-class>
      </interceptor>
  </interceptors>
  <assembly-descriptor>
      <interceptor-binding>
          <ejb-name>OrderBean</ejb-name>
          <interceptor-order>
              <interceptor-class>
                  com.devchronicles.SecurityInterceptor
              </interceptor-class>
          </interceptor-order>
          <interceptor-class>
              com.devchronicles.AnotherInterceptor
          </interceptor-class>
      </interceptor-binding>
  </assembly-descriptor>
</ejb-jar>

When there is more than one class-level interceptor, the interceptors follow the order in which they are listed in the @Interceptors annotation:

@Interceptors(SecurityInterceptor.class, AnotherInterceptor.class)
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)public class SomeBusinessService {    
	public void startService(){
	// ...

Finally, if more than one method-level interceptor is present, again, the interceptors follow the order they are listed in the @Interceptors annotation:

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class SomeBusinessService {

    @Interceptors(SecurityInterceptor.class, AnotherInterceptor.class)
    public void startService(){
    // ...

If you need to tweak the default ordering, you can do so by custom configuration within the EJB-JAR XML. The following overrides the interceptor order and provides a custom ordering:

<ejb-jar...>
  <interceptors>
      <interceptor>
          <interceptor-class>
              com.devchronicles.SecurityInterceptor
          </interceptor-class>
      </interceptor>
  </interceptors>
  <assembly-descriptor>
      <interceptor-binding>
          <ejb-name>OrderBean</ejb-name>
          <interceptor-order>
              <interceptor-class>
                 com.devchronicles.SecurityInterceptor
              </interceptor-class>
          </interceptor-order>
          <interceptor-class>
              com.devchronicles.AnotherInterceptor
          </interceptor-class>
          <method>
                <method-name>startService</method-name>
          </method>
      </interceptor-binding>
  </assembly-descriptor>
>/ejb-jar<

There might be exceptional cases in which the interceptors need to be disabled. You can disable interceptors with annotations as seen in Listing 8-9. Java EE offers two different annotations to disable default and class-level interceptors separately.

Still, the example given is only valid in EJB and MDBs, which may not be enough for all cases. Thanks to CDI, it is not hard to achieve more.

CDI Interceptors

Before CDI, interceptors were applicable only for EJB and MDBs. CDI unleashed a huge power and transformed interceptors into an AOP-capable feature that works on any object.

Implementing CDI interceptors is straightforward and quite flexible. First, you need to specify a binding. A binding is a custom annotation annotated with @InterceptorBinding.

@InterceptorBinding
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface Secure {}

The @InterceptorBinding is used to bind interceptors with the target code. Next, you can implement and annotate the interceptor with the custom binding. CDI interceptors are implemented the same way as the EJB interceptors, the only significant difference being the use of the binding annotation which can be seen in Listing 8-10

Just like the EJB interceptors, the @Interceptor annotation needs to be used to promote the class file to an interceptor. The @Secure annotation binds the interceptor. Finally, the @AroundInvoke annotation marks the method to be executed during intercepted calls.

The next step is to implement the annotation on an advice, as shown in Listing 8-11.

CDI interceptors require one additional step of declaring the interceptors in the beans.xml file. This is one of the rare cases in which you need to use XML configuration; it’s used to determine the execution order of the interceptors.

Interceptor bindings can include other interceptor bindings that wrap multiple bindings together. The CDI container is not started if the beans.xml file is missing:

<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/javaeehttp://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
  <interceptors>
      <class> com.devchronicles.interceptor.SecurityInterceptor</class>
      <class> com.devchronicles.interceptor.SomeOtherInterceptor</class>
  </interceptors>
</beans>

Although the declaration order of the binding annotations may imply a sense of execution order, in reality it has no effect. The execution order of the interceptor depends on the declaration order in the beans.xml file.

Mixing CDI and EJB interceptors may create ambiguity in the ordering. As a rule, EJB interceptors execute before CDI interceptors do.

Intercepting methods creates complexity, but creating multiple bindings and mixing CDI and EBJ interceptors brings this complexity to the next level. Complex interceptor structures may expose a complex application architecture for developers who are not familiar with the code.

WHERE AND WHEN TO USE INTERCEPTORS

AOP is a popular programming paradigm that can help to implement and encapsulate cross-cutting concerns. In many cases, AOP can really shine. Logging, auditing, security, and other repeating nonbusiness behavior are good candidates.

Interceptors in Java EE are powerful tools that allow you to implement AOP without the need for a third-party framework. With the introduction of CDI interceptors, Java EE has become more complete and capable. Implementing an interceptor may require some XML configuration, unlike other patterns listed in this book. However, the configuration is only limited to provide ordering, which other patterns such as decorators may also require.

Interceptors can address many cross-cutting concerns. They provide a clean implementation while encapsulating the common concern. However, interceptors can be troublesome if they change business behavior. If this happens, the business logic is distributed between the class and the interceptor. The business method becomes unreadable and misleading because it doesn’t expose the whole logic. Additionally, it unnecessarily complicates the architecture and application flow. Besides, debugging is almost impossible and complicated.

Readability and self-documenting code is an important aim, and misuse of interceptors can cause great harm if it consists of business logic. However, using interceptors for nonbusiness and repeating behavior can simplify the business methods and help greatly.

As a general rule, avoid using interceptors for injecting business logic or changing the execution behavior. Interceptors are great when you need repetitive work that covers some methods or classes.

SUMMARY

AOP is a popular subject that has many supporters but also many enemies. As expected, it is not a panacea that solves all problems. Aspects can greatly reduce code readability and complicate the overall application flow if not used properly.

However, aspects can be magical tools that implement additional behavior to the existing code base with minimal effort. You can easily turn them on or off depending on the running time environment. For example, you can turn off a logging aspect during development and put it into action in the test environment.

Java EE offers simple interceptors that support annotations and need little XML configuration, except in special cases. You can use interceptors both in EJB and MDB contexts either at the class or the method levels. You can also declare interceptors at a default level, which targets all EJBs matching the given criteria. The default level and ordering needs some configuration to be done in the EJB-JAR XML file.

CDI adds great extensibility and functionality to interceptors. You can easily customize CDI interceptors and use them in a cleaner way with the @InterceptorBinding annotation. You can use interceptor bindings to wrap other interceptor bindings, forming a chain of interceptors to execute. CDI interceptors do need minimal XML configuration to help the CDI container determine the execution order.

EJB and CDI interceptors work alone or together in harmony. They offer all the functionality needed to implement AOP without a third-party framework.

The proper use of interceptors creates beautifully crafted applications with magical execution flow. When it is time to decide to implement interceptors, make sure they don’t change business flow and contain application logic.

NOTES

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.114.92