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
<?xml version="1.0"?> <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 GitHub repository – download it there or check it out using Git:
git clone https://github.com/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>