Java EE 6, GlassFish and the Interceptor API

August 17th, 2011 by

Aspect oriented programming and the definition of cross-cutting-concerns is made easy in Java EE 6 using interceptors.

In the following tutorial we’re going to take a look at the different possibilities to apply interceptors to your EJBs at class or method level and how to setup a GlassFish instance to run the examples.

 

Prerequisites

We don’t need much for the following tutorial – just a JDK, Maven and GlassFish…

Setting up a new project

Nearly every tutorial of mine is beginning with the setup of a new Maven project and this one is no exception ..

  • Create a new Maven project using your Maven-enabled IDE of choice or via console using
    mvn archetype:generate
  • I am using the archetype org.codehaus.mojo.archetypes:webapp-javaee6 here because it contains all dependencies needed for this tutorial
  • My final pom.xml looks like this now
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     
     <groupId>com.hascode.tutorial.jee</groupId>
     <artifactId>jee6-interceptor-tutorial</artifactId>
     <version>0.0.1-SNAPSHOT</version>
     <packaging>war</packaging>
     
     <name>jee6-interceptor-tutorial</name>
     
     <properties>
     <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
     
     <dependencies>
     <dependency>
     <groupId>javax</groupId>
     <artifactId>javaee-web-api</artifactId>
     <version>6.0</version>
     <scope>provided</scope>
     </dependency>
     
     <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.8.2</version>
     <scope>test</scope>
     </dependency>
     </dependencies>
     
     <build>
     <plugins>
     <plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-compiler-plugin</artifactId>
     <version>2.3.2</version>
     <configuration>
     <source>1.6</source>
     <target>1.6</target>
     <compilerArguments>
     <endorseddirs>${endorsed.dir}</endorseddirs>
     </compilerArguments>
     </configuration>
     </plugin>
     <plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-war-plugin</artifactId>
     <version>2.1.1</version>
     <configuration>
     <failOnMissingWebXml>false</failOnMissingWebXml>
     </configuration>
     </plugin>
     <plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-dependency-plugin</artifactId>
     <version>2.1</version>
     <executions>
     <execution>
     <phase>validate</phase>
     <goals>
     <goal>copy</goal>
     </goals>
     <configuration>
     <outputDirectory>${endorsed.dir}</outputDirectory>
     <silent>true</silent>
     <artifactItems>
     <artifactItem>
     <groupId>javax</groupId>
     <artifactId>javaee-endorsed-api</artifactId>
     <version>6.0</version>
     <type>jar</type>
     </artifactItem>
     </artifactItems>
     </configuration>
     </execution>
     </executions>
     </plugin>
     </plugins>
     </build>
    </project>

Now that we’ve got a “mavenized” project we’re going to take a look at the interceptor API …

In-Class Around-Invoke Interceptors

The fastest way to wrap all methods of a class with an interceptor is to use the @AroundInvoke annotation with an intercepting method that must preserve the following restrictions:

  • the method must not be final or static
  • the method must have a javax.interceptor.InvocationContext parameter and must return Object (= the result of the invoked target method)
  • the method may throw a checked exception

That’s the contract .. now to implement our first interceptor … we’re creating a servlet that gets a stateless session bean injected whose methods are intercepted using @AroundInvoke. Finally some information is written to our logs and we’re able to see them in the GlassFish log viewer…

  • First our servlet .. we’re using @EJB to inject the BookEJB into the servlet. The servlet executes the EJB’s createBook and findBookByTitle methods
    package com.hascode.tutorial.jee;
     
    import java.io.IOException;
    import java.util.logging.Logger;
     
    import javax.ejb.EJB;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    @WebServlet(name="bookServlet", urlPatterns="/printBookInformation")
    public class BookServlet extends HttpServlet {
     
     @EJB
     private BookEJB bookEJB;
     
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse res)
     throws ServletException, IOException {
     Book book = new Book();
     book.setTitle("The joy of interceptors v.1");
     bookEJB.createBook(book);
     bookEJB.findBookByTitle("test");
     res.getWriter().append("Watch your logs..");
     }
     
    }
  • In the next step we’re creating a stateless session bean whose methods are intercepted
    package com.hascode.tutorial.jee;
     
    import java.util.HashSet;
    import java.util.Set;
    import java.util.logging.Level;
    import java.util.logging.Logger;
     
    import javax.ejb.Stateless;
    import javax.interceptor.AroundInvoke;
    import javax.interceptor.InvocationContext;
     
    @Stateless
    public class BookEJB {
     private final Logger logger = Logger.getLogger("com.hascode.tutorial.jee");
     
     public void createBook(final Book book) {
     logger.log(Level.INFO, "creating a new book with title {0} in BookEJB",
     book.getTitle());
     // persist
     }
     
     public Set<Book> findBookByTitle(final String title) {
     logger.log(Level.INFO,
     "searching book with given title {0} in BookEJB", title);
     // find
     return new HashSet<Book>();
     }
     
     @AroundInvoke
     private Object logMethodExecution(final InvocationContext ic)
     throws Exception {
     logger.log(Level.INFO,
     "interceptor invoked on target {0} and method {1}",
     new Object[] { ic.getTarget().toString(),
     ic.getMethod().getName() });
     try {
     return ic.proceed();
     } finally {
     }
     }
    }
  • Build the project using
    mvn package
  • Deploy the application as described in the following part and open the following URL in your browser: http://localhost:8080/jee-intercept/printBookInformation

  • Now take a look at your GlassFish logs by looking in your logfile “glassfish/domains/jee6-interceptor-tutorial/logs/server.log” or – better – using GlassFish’s log viewer in the administration web interface. Simply click on Enterprise Server > View Log Files

GlassFish Configuration and Application Deployment

Assuming that we’ve got a GlassFish Application Server installed, we’re going to create a new domain for the following examples …

  • Run asadmin to start GlassFish’s administration console and use create-domain and start-domain to create a new domain and start it afterwards
    asadmin> create-domain jee6-interceptor-tutorial
    Enter admin user name [Enter to accept default "admin" / no password]>
    Using port 4848 for Admin.
    Using default port 8080 for HTTP Instance.
    Using default port 7676 for JMS.
    Using default port 3700 for IIOP.
    Using default port 8181 for HTTP_SSL.
    Using default port 3820 for IIOP_SSL.
    Using default port 3920 for IIOP_MUTUALAUTH.
    Using default port 8686 for JMX_ADMIN.
    Using default port 6666 for OSGI_SHELL.
    Distinguished Name of the self-signed X.509 Server Certificate is:
    [CN=hostname,OU=GlassFish,O=Oracle Corporation,L=Santa Clara,ST=California,C=US]
    No domain initializers found, bypassing customization step
    Domain jee6-interceptor-tutorial created.
    Domain jee6-interceptor-tutorial admin port is 4848.
    Domain jee6-interceptor-tutorial allows admin login as user "admin" with no password.
    Command create-domain executed successfully.
     
    asadmin> start-domain jee6-interceptor-tutorial
    Waiting for DAS to start ...................
    Started domain: jee6-interceptor-tutorial
    Domain location: /somepath/glassfishv3/glassfish/domains/jee6-interceptor-tutorial
    Log file: /somepath/glassfishv3/glassfish/domains/jee6-interceptor-tutorial/logs/server.log
    Admin port for the domain: 4848
    Command start-domain executed successfully.
  • We’re now able to log into the administration web console at http://localhost:4848
  • Click on  “Applications” in the navigation and deploy the application by uploading the war-file from your project’s target directory

Method Interceptors

In the following example we’re going to use an external class as an interceptor and we’re going to intercept only one method in a stateless session bean using the @Interceptors annotation..

  • First we’re creating another servlet to run the intercepted EJB’s methods named UserServlet
    package com.hascode.tutorial.jee;
     
    import java.io.IOException;
     
    import javax.ejb.EJB;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    @WebServlet(name = "userServlet", urlPatterns = "/printUserInformation")
    public class UserServlet extends HttpServlet {
     
     @EJB
     private UserEJB userEJB;
     
     @Override
     protected void doGet(final HttpServletRequest req,
     final HttpServletResponse res) throws ServletException, IOException {
     User user = new User();
     user.setName("Alfred E. Neumann");
     userEJB.createUser(user);
     userEJB.findBookByTitle("Charles Bukowski");
     res.getWriter().append("Watch your logs..");
     }
     
    }
  • In the next step we’re adding another stateless session bean named UserEJB whose createUser method is intercepted
    package com.hascode.tutorial.jee;
     
    import java.util.HashSet;
    import java.util.Set;
    import java.util.logging.Level;
    import java.util.logging.Logger;
     
    import javax.ejb.Stateless;
    import javax.interceptor.Interceptors;
     
    @Stateless
    public class UserEJB {
     private final Logger logger = Logger.getLogger("com.hascode.tutorial.jee");
     
     @Interceptors(ProtocolInterceptor.class)
     public void createUser(final User user) {
     logger.log(Level.INFO, "creating a new user with name {0} in UserEJB",
     user.getName());
     // persist
     }
     
     public Set<User> findBookByTitle(final String name) {
     logger.log(Level.INFO, "searching user with given name {0} in UserEJB",
     name);
     // find
     return new HashSet<User>();
     }
    }
  • Now we need to create the referenced interceptor class, ProtocolInterceptor
    package com.hascode.tutorial.jee;
     
    import java.util.logging.Level;
    import java.util.logging.Logger;
     
    import javax.interceptor.AroundInvoke;
    import javax.interceptor.InvocationContext;
     
    public class ProtocolInterceptor {
     private final Logger logger = Logger.getLogger("com.hascode.tutorial.jee");
     
     @AroundInvoke
     private Object logMethodExecution(final InvocationContext ic)
     throws Exception {
     logger.log(Level.INFO,
     "protocol interceptor invoked on target {0} and method {1}",
     new Object[] { ic.getTarget().toString(),
     ic.getMethod().getName() });
     try {
     return ic.proceed();
     } finally {
     }
     }
    }
  • We’re now running the servlet – and in the logs we’re able to see that only the createUser method is intercepted
    [#|2011-08-16T20:12:10.492+0200|INFO|glassfish3.0.1|javax.enterprise.system.tools.admin.org.glassfish.deployment.admin|_ThreadID=25;_ThreadName=Thread-1;|jee6-interceptor-tutorial-0.0.1-SNAPSHOT was suc
    cessfully deployed in 232 milliseconds.|#]
    [#|2011-08-16T20:12:31.404+0200|INFO|glassfish3.0.1|com.hascode.tutorial.jee|_ThreadID=28;_ThreadName=Thread-1;|protocol interceptor invoked on target com.hascode.tutorial.jee.UserEJB@1e131e5 and method
     createUser|#]
    [#|2011-08-16T20:12:31.404+0200|INFO|glassfish3.0.1|com.hascode.tutorial.jee|_ThreadID=28;_ThreadName=Thread-1;|creating a new user with name Alfred E. Neumann in UserEJB|#]
    [#|2011-08-16T20:12:31.404+0200|INFO|glassfish3.0.1|com.hascode.tutorial.jee|_ThreadID=28;_ThreadName=Thread-1;|searching user with given name Charles Bukowski in UserEJB|#]

Class Interceptors

To apply an interceptor to every method of a specified class, just apply the @Interceptors annotation at class level instead of method level ..

  • The following example adds the ProtocolInterceptor from the preciding example to every method of the class ProductEJB
    @Stateless
    @Interceptors(ProtocolInterceptor.class)
    public class ProductEJB {
    }

Mixing, Chaining, Excluding Interceptors

Mixing @Interceptors at class and method level we’re able to apply several chained interceptors to the methods of a class. Using @ExcludeClassInterceptors disables the default interceptors of a class for a given method. Let us build some examples …

  • First we’re implementing our obligatory servlet to execute the EJB’s methods named ProductServlet
    package com.hascode.tutorial.jee;
     
    import java.io.IOException;
     
    import javax.ejb.EJB;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
     
    @WebServlet(name = "productServlet", urlPatterns = "/printProductInformation")
    public class ProductServlet extends HttpServlet {
     
     @EJB
     private ProductEJB productEJB;
     
     @Override
     protected void doGet(final HttpServletRequest req,
     final HttpServletResponse res) throws ServletException, IOException {
     Product product = new Product();
     product.setName("Extra strong coffee");
     productEJB.createProduct(product);
     productEJB.findProductByName("sugar");
     res.getWriter().append("Watch your logs..");
     }
     
    }
  • Furthermore we’re creating another stateless session bean that has two interceptors applied at class level (Interceptor1, Interceptor3), two interceptors at method level applied to the createProduct method and one interceptor at method level applied to the method findProductByName that us also marked with @ExcludeClassInterceptors. That’s the final EJB named ProductEJB
    package com.hascode.tutorial.jee;
     
    import java.util.HashSet;
    import java.util.Set;
    import java.util.logging.Level;
    import java.util.logging.Logger;
     
    import javax.ejb.Stateless;
    import javax.interceptor.ExcludeClassInterceptors;
    import javax.interceptor.Interceptors;
     
    @Stateless
    @Interceptors({ Interceptor1.class, Interceptor3.class })
    public class ProductEJB {
     private final Logger logger = Logger.getLogger("com.hascode.tutorial.jee");
     
     @Interceptors({ Interceptor2.class, Interceptor4.class })
     public void createProduct(final Product product) {
     logger.log(Level.INFO,
     "creating a new product with name {0} in ProductEJB",
     product.getName());
     // persist
     }
     
     @ExcludeClassInterceptors
     @Interceptors(Interceptor2.class)
     public Set<Product> findProductByName(final String name) {
     logger.log(Level.INFO,
     "searching product with given name {0} in ProductEJB", name);
     // find
     return new HashSet<Product>();
     }
    }
  • If we execute the servlet now and take a look at the logs/log viewer we’re able to see that the order of execution is, as expected: interceptor #1, #3, #2, #4, #2

Tutorial Sources

I have put the source from this tutorial on my Bitbucket repository – download it there or check it out using Mercurial:

hg clone https://bitbucket.org/hascode/hascode-tutorials

Troubleshooting

  • Plugin execution not covered by lifecycle configuration: org.apache.maven.plugins:maven-ear-plugin:2.4.2:generate-application-xml (execution: default-generate-application-xml, phase: generate-resources)” – Downgrade your m2eclipse Plugin version or use the quick fix offered and silence m2eclipse. Detailed information on this can be found on this wiki page – the following lines will help you there
    <pluginManagement>
     <plugins>
     <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
     <plugin>
     <groupId>org.eclipse.m2e</groupId>
     <artifactId>lifecycle-mapping</artifactId>
     <version>1.0.0</version>
     <configuration>
     <lifecycleMappingMetadata>
     <pluginExecutions>
     <pluginExecution>
     <pluginExecutionFilter>
     <groupId>
     org.apache.maven.plugins
     </groupId>
     <artifactId>
     maven-ear-plugin
     </artifactId>
     <versionRange>
     [2.4.2,)
     </versionRange>
     <goals>
     <goal>
     generate-application-xml
     </goal>
     </goals>
     </pluginExecutionFilter>
     <action>
     <ignore></ignore>
     </action>
     </pluginExecution>
     </pluginExecutions>
     </lifecycleMappingMetadata>
     </configuration>
     </plugin>
     </plugins>
    </pluginManagement>

Resources

Tags: , , , , , , ,

Search
Categories