Simple Aspect Oriented Programming (AOP) using CDI in JavaEE
We write service APIs which cater to certain business logic. There are few cross-cutting concerns that cover all service APIs like Security, Logging, Auditing, Measuring Latencies and so on. This is a repetitive non-business code which can be reused among other methods. One way to reuse is to move these repetitive code into its own methods and invoke them in the service APIs somethings like:
public class MyService{ public ServiceModel service1(){ isAuthorized(); //execute business logic. } } public class MyAnotherService{ public ServiceModel service1(){ isAuthorized(): //execute business logic. } }
The above approach will work but not without creating code noise, mixing cross-cutting concerns with the business logic. There is another approach to solve the above requirements which is by using Aspect and this approach is called Aspect Oriented Programming (AOP). There are a different ways you can make use of AOP – by using Spring AOP, JavaEE AOP. In this example I Will try to use AOP using CDI in Java EE applications. To explain this I have picked a very simple example of building a web application to fetch few records from Database and display in the browser.
Creating the Data access layer
The table structure is:
create table people( id INT NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, place varchar(100), primary key(id));
Lets create a Model class to hold a person information
package demo.model; public class Person{ private String id; private String name; private String place; public String getId(){ return id; } public String setId(String id) { this.id = id;} public String getName(){ return name; } public String setName(String name) { this.name = name;} public String getPlace(){ return place; } public String setPlace(String place) { this.place = place;} }
Lets create a Data Access Object which exposes two methods –
- to fetch the details of all the people
- to fetch the details of one person of given id
package demo.dao; import demo.common.DatabaseConnectionManager; import demo.model.Person; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; public class PeopleDAO { public List<Person> getAllPeople() throws SQLException { String SQL = "SELECT * FROM people"; Connection conn = DatabaseConnectionManager.getConnection(); List<Person> people = new ArrayList<>(); try (Statement statement = conn.createStatement(); ResultSet rs = statement.executeQuery(SQL)) { while (rs.next()) { Person person = new Person(); person.setId(rs.getString("id")); person.setName(rs.getString("name")); person.setPlace(rs.getString("place")); people.add(person); } } return people; } public Person getPerson(String id) throws SQLException { String SQL = "SELECT * FROM people WHERE id = ?"; Connection conn = DatabaseConnectionManager.getConnection(); try (PreparedStatement ps = conn.prepareStatement(SQL)) { ps.setString(1, id); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { Person person = new Person(); person.setId(rs.getString("id")); person.setName(rs.getString("name")); person.setPlace(rs.getString("place")); return person; } } } return null; } }
You can use your own approach to get a new Connection. In the above code I have created a static utility that returns me the same connection.
Creating Interceptors
Creating Interceptors involves 2 steps:
- Create Interceptor binding which creates an annotation annotated with
@InterceptorBinding
that is used to bind the interceptor code and the target code which needs to be intercepted. - Create a class annotated with
@Interceptor
which contains the interceptor code. It would contain methods annotated with@AroundInvoke
, different lifecycle annotations,@AroundTimeout
and others.
Lets create an Interceptor binding by name @LatencyLogger
package demo; import java.lang.annotation.Target; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.*; import static java.lang.annotation.ElementType.*; import javax.interceptor.InterceptorBinding; @InterceptorBinding @Retention(RUNTIME) @Target({METHOD, TYPE}) public @interface LatencyLogger { }
Now we need to create the Interceptor code which is annotated with @Interceptor
and also annotated with the Interceptor binding we created above i.e @LatencyLogger
:
package demo; import java.io.Serializable; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; import javax.interceptor.InvocationContext; @Interceptor @LatencyLogger public class LatencyLoggerInterceptor implements Serializable{ @AroundInvoke public Object computeLatency(InvocationContext invocationCtx) throws Exception{ long startTime = System.currentTimeMillis(); //execute the intercepted method and store the return value Object returnValue = invocationCtx.proceed(); long endTime = System.currentTimeMillis(); System.out.println("Latency of " + invocationCtx.getMethod().getName() +": " + (endTime-startTime)+"ms"); return returnValue; } }
There are two interesting things in the above code:
- use of
@AroundInvoke
- parameter of type
InvocationContext
passed to the method
@AroundInvoke
designates the method as an interceptor method. An Interceptor class can have only ONE method annotated with this annotation. When ever a target method is intercepted, its context is passed to the interceptor. Using the InvocationContext
one can get the method details, the parameters passed to the method.
We need to declare the above Interceptor in the WEB-INF/beans.xml file
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" bean-discovery-mode="all"> <interceptors> <class>demo.LatencyLoggerInterceptor</class> </interceptors> </beans>
Creating Service APIs annotated with Interceptors
We have already created the Interceptor binding and the interceptor which gets executed. Now lets create the Service APIs and then annotate them with the Interceptor binding
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package demo.service; import demo.LatencyLogger; import demo.dao.PeopleDAO; import demo.model.Person; import java.sql.SQLException; import java.util.List; import javax.inject.Inject; public class PeopleService { @Inject PeopleDAO peopleDAO; @LatencyLogger public List<Person> getAllPeople() throws SQLException { return peopleDAO.getAllPeople(); } @LatencyLogger public Person getPerson(String id) throws SQLException { return peopleDAO.getPerson(id); } }
We have annotated the service methods with the Interceptor binding @LatencyLogger
. The other way would be to annotate at the class level which would then apply the annotation to all the methods of the class. Another thing to notice is the @Inject
annotation that injects the instance i.e injects the dependency into the class.
Next is to wire up the Controller and View to show the data. The controller is the servlet and view is a plain JSP using JSTL tags.
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package demo; import demo.model.Person; import demo.service.PeopleService; import java.io.IOException; import java.sql.SQLException; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.inject.Inject; 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 = "AOPDemo", urlPatterns = {"/AOPDemo"}) public class AOPDemoServlet extends HttpServlet { @Inject PeopleService peopleService; @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { List<Person> people = peopleService.getAllPeople(); Person person = peopleService.getPerson("2"); request.setAttribute("people", people); request.setAttribute("person", person); getServletContext().getRequestDispatcher("/index.jsp").forward(request, response); } catch (SQLException ex) { Logger.getLogger(AOPDemoServlet.class.getName()).log(Level.SEVERE, null, ex); } } }
The above servlet is available at http://localhost:8080/
/AOPDemo. It fetches the data and redirects to the view to display the same. Note that the Service has also been injected using @Inject
annotation. If the dependencies are not injected and instead created using new
then the Interceptors will not work. This is an important point which I realised while building this sample.
The JSP to render the data would be
<%@page contentType="text/html" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>AOP Demo</title> </head> <body> <h1>Hello World!</h1> <table> <tr> <th>Id</th> <th>Name</th> <th>Place</th> </tr> <c:forEach items="${requestScope.people}" var="person"> <tr> <td><c:out value="${person.id}"/></td> <td><c:out value="${person.name}"/></td> <td><c:out value="${person.place}"/></td> </tr> </c:forEach> </table> <br/> Details for person with id=2 <c:out value="Name ${person.name} from ${person.place}" /> </body> </html>
With this you would have built a very simple app using Interceptors. Thanks for reading and staying with me till this end. Please share your queries/feedback as comments. And also share this article among your friends!
Reference: | Simple Aspect Oriented Programming (AOP) using CDI in JavaEE from our JCG partner Mohamed Sanaulla at the Experiences Unlimited blog. |
Mohamed,
Thank you for posting. Found this to be very helpful. I have a question. You mention that we must use @Inject to utilize this. How exactly is that done?
Java EE framework takes care of instantiation of that particular variable annotated with @Inject. It identifies the type of object to be instantiated from the type of the variable declared.
Yet another AOP == Logging example.