AOP

Audit Framework with Spring AOP and JPA

Posted on Updated on

Every now and then we come across a common requirement of building an Audit framework. In simple words it means whenever a record is added, edited or removed there should be an entry made in a separate table so that different actions of users can be monitored. Today in this tutorial we will build an audit framework in Java using Spring AOP and JPA. Records will be saved in xml form. We will reuse code from JPA OneToMany Mapping Using Hibernate and MySQL tutorial.

Our database schema consists of BookShelf, Book and Audit.

Database Schema

First we will setup our maven dependency (Need help creating Maven Java Project in Eclipse ?)

pom.xml

<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>AuditFrameWork</groupId>
	<artifactId>AuditFrameWork</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-clean-plugin</artifactId>
				<version>2.5</version>
			</plugin>
		</plugins>
	</build>
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
			<version>2.2.2</version>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>1.6.11</version>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.6.11</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.21</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate.javax.persistence</groupId>
			<artifactId>hibernate-jpa-2.0-api</artifactId>
			<version>1.0.1.Final</version>
		</dependency>
		<dependency>
			<groupId>com.thoughtworks.xstream</groupId>
			<artifactId>xstream</artifactId>
			<version>1.4.2</version>
		</dependency>
		<dependency>
			<groupId>commons-lang</groupId>
			<artifactId>commons-lang</artifactId>
			<version>2.6</version>
		</dependency>
		<dependency>
			<groupId>commons-collections</groupId>
			<artifactId>commons-collections</artifactId>
			<version>3.2</version>
		</dependency>
		<dependency>
			<groupId>commons-beanutils</groupId>
			<artifactId>commons-beanutils</artifactId>
			<version>1.8.3</version>
		</dependency>
		<dependency>
			<groupId>javax.enterprise</groupId>
			<artifactId>cdi-api</artifactId>
			<version>1.0</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>${hibernate.version}</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>${hibernate.version}</version>
			<exclusions>
				<exclusion>
					<groupId>cglib</groupId>
					<artifactId>cglib</artifactId>
				</exclusion>
				<exclusion>
					<groupId>dom4j</groupId>
					<artifactId>dom4j</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>
	<properties>
		<hibernate.version>3.6.7.Final</hibernate.version>
		<spring.version>3.1.2.RELEASE</spring.version>
	</properties>
</project>

Second we will start with our JPA mapping code. There are few important classes and interfaces involved.

  1. IEntity – A marker interface to depict an database entity
  2. IXMLConvertable – As name suggests this interface let’s conversion from Java to XML generic
  3. IAuditable – A marker interface to depict an entity which can be auditable

Let’s start building our framework.

IAuditable


package com.mumz.test.audit.interfaces;

import java.io.Serializable;

/**
 * The Interface IAuditable.
 * 
 * @author prabhat.jha
 */
public interface IAuditable extends Serializable{

	/** The Constant OPERATION_INSERT. */
	public static final String OPERATION_INSERT = "Insert";
	
	/** The Constant OPERATION_UPDATE. */
	public static final String OPERATION_UPDATE = "Update";
	
	/** The Constant OPERATION_DELETE. */
	public static final String OPERATION_DELETE = "Delete";
}

IEntity


package com.mumz.test.audit.interfaces;

import java.io.Serializable;

/**
 * The Interface IEntity.
 * @author prabhat.jha
 */
public interface IEntity extends Serializable{
}

IXMLConvertable


package com.mumz.test.audit.interfaces;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

/**
 * The Interface IXMLConvertable.
 * @author prabhat.jha
 */
public interface IXMLConvertable extends Serializable {
	
	/**
	 * Gets the class alias.
	 * 
	 * @return the class alias
	 */
	public String getClassAlias();

	/**
	 * Gets the field to be omitted.
	 * 
	 * @return the field to be omitted
	 */
	public List<String> getFieldsToBeOmitted();

	/**
	 * Gets the field aliases.
	 * 
	 * @return the field aliases
	 */
	public Map<String, String> getFieldAliases();
}

With the basic structure of our code base setup we will reuse the code from this post. Explanation on OneToMany can be obtained from the link.

Book.java

package com.mumz.test.audit.beans;

import java.util.List;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Transient;

import com.mumz.test.audit.interfaces.IAuditable;
import com.mumz.test.audit.interfaces.IEntity;
import com.mumz.test.audit.interfaces.IXMLConvertable;

/**
 * The Class Book.
 * 
 * @author prabhat.jha
 */
@Entity
@Table(name = "BOOK")
@NamedQueries({ @NamedQuery(name = "fetchAllBooks", query = "SELECT ALLBOOKS FROM Book ALLBOOKS") })
public class Book implements IXMLConvertable, IEntity, IAuditable {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = -4788522141255171404L;

	/** The id. */
	private Long id = null;

	/** The name. */
	private String name = null;

	/** The author. */
	private String author = null;

	/** The book shelf. */
	private BookShelf bookShelf = null;

	/**
	 * Gets the id.
	 * 
	 * @return the id
	 */
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "BOOK_ID")
	public Long getId() {
		return id;
	}

	/**
	 * Sets the id.
	 * 
	 * @param id
	 *            the id to set
	 */
	public void setId(Long id) {
		this.id = id;
	}

	/**
	 * Gets the name.
	 * 
	 * @return the name
	 */
	@Column(name = "BOOK_NAME")
	public String getName() {
		return name;
	}

	/**
	 * Sets the name.
	 * 
	 * @param name
	 *            the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Gets the author.
	 * 
	 * @return the author
	 */
	@Column(name = "BOOK_AUTHOR")
	public String getAuthor() {
		return author;
	}

	/**
	 * Sets the author.
	 * 
	 * @param author
	 *            the author to set
	 */
	public void setAuthor(String author) {
		this.author = author;
	}

	/**
	 * Gets the book shelf.
	 * 
	 * @return the bookShelf
	 */
	@ManyToOne
	@JoinColumn(name = "BOOK_SHELF_ID")
	public BookShelf getBookShelf() {
		return bookShelf;
	}

	/**
	 * Sets the book shelf.
	 * 
	 * @param bookShelf
	 *            the bookShelf to set
	 */
	public void setBookShelf(BookShelf bookShelf) {
		this.bookShelf = bookShelf;
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.interfaces.IXMLConvertable#getClassAlias()
	 */
	@Override
	@Transient
	public String getClassAlias() {
		return "Book";
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.interfaces.IXMLConvertable#getFieldsToBeOmitted()
	 */
	@Override
	@Transient
	public List<String> getFieldsToBeOmitted() {
		return null;
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.interfaces.IXMLConvertable#getFieldAliases()
	 */
	@Override
	@Transient
	public Map<String, String> getFieldAliases() {
		return null;
	}

	/**
	 * (non-Javadoc).
	 * 
	 * @return the int
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((author == null) ? 0 : author.hashCode());
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	/**
	 * (non-Javadoc).
	 * 
	 * @param obj
	 *            the obj
	 * @return true, if successful
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof Book)) {
			return false;
		}
		Book other = (Book) obj;
		if (author == null) {
			if (other.author != null) {
				return false;
			}
		} else if (!author.equals(other.author)) {
			return false;
		}
		if (id == null) {
			if (other.id != null) {
				return false;
			}
		} else if (!id.equals(other.id)) {
			return false;
		}
		if (name == null) {
			if (other.name != null) {
				return false;
			}
		} else if (!name.equals(other.name)) {
			return false;
		}
		return true;
	}

	/**
	 * (non-Javadoc).
	 * 
	 * @return the string
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return String.format("Book [id=%s, name=%s, author=%s]", id, name,
				author);
	}
}

BookShelf.java


package com.mumz.test.audit.beans;

import java.util.List;
import java.util.Map;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;

import com.mumz.test.audit.interfaces.IEntity;
import com.mumz.test.audit.interfaces.IXMLConvertable;

/**
 * The Class BookShelf.
 * @author prabhat.jha
 */
@Entity
@Table(name = "BOOK_SHELF")
@NamedQueries({
	@NamedQuery(name="fetchAllBookShelves", query="SELECT ALLBOOKSHELVES FROM BookShelf ALLBOOKSHELVES")
})
public class BookShelf implements IEntity, IXMLConvertable{
	
	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = -7867320637075813912L;

	/** The id. */
	private Long	id		= null;

	/** The name. */
	private String	name	= null;
	
	/** The books. */
	private List<Book> books = null;

	/**
	 * Gets the id.
	 * 
	 * @return the id
	 */
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	@Column(name="BOOK_SHELF_ID")
	public Long getId() {
		return id;
	}

	/**
	 * Sets the id.
	 * 
	 * @param id
	 *            the id to set
	 */
	public void setId(Long id) {
		this.id = id;
	}

	/**
	 * Gets the name.
	 * 
	 * @return the name
	 */
	@Column(name="BOOK_SHELF_NAME")
	public String getName() {
		return name;
	}

	/**
	 * Sets the name.
	 * 
	 * @param name
	 *            the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Gets the books.
	 * 
	 * @return the books
	 */
	@OneToMany(cascade=CascadeType.ALL, mappedBy="bookShelf")
	public List<Book> getBooks() {
		return books;
	}

	/**
	 * Sets the books.
	 * 
	 * @param books
	 *            the books to set
	 */
	public void setBooks(List<Book> books) {
		this.books = books;
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.interfaces.IXMLConvertable#getClassAlias()
	 */
	@Override
	@Transient
	public String getClassAlias() {
		return "BookShelf";
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.interfaces.IXMLConvertable#getFieldsToBeOmitted()
	 */
	@Override
	@Transient
	public List<String> getFieldsToBeOmitted() {
		return null;
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.interfaces.IXMLConvertable#getFieldAliases()
	 */
	@Override
	@Transient
	public Map<String, String> getFieldAliases() {
		return null;
	}
	/**
	 * (non-Javadoc).
	 * 
	 * @return the int
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((books == null) ? 0 : books.hashCode());
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	/**
	 * (non-Javadoc).
	 * 
	 * @param obj
	 *            the obj
	 * @return true, if successful
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof BookShelf)) {
			return false;
		}
		BookShelf other = (BookShelf) obj;
		if (books == null) {
			if (other.books != null) {
				return false;
			}
		} else if (!books.equals(other.books)) {
			return false;
		}
		if (id == null) {
			if (other.id != null) {
				return false;
			}
		} else if (!id.equals(other.id)) {
			return false;
		}
		if (name == null) {
			if (other.name != null) {
				return false;
			}
		} else if (!name.equals(other.name)) {
			return false;
		}
		return true;
	}

	/**
	 * (non-Javadoc).
	 * 
	 * @return the string
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return String.format("BookShelf [id=%s, name=%s, books=%s]", id, name, books);
	}
}

And our Audit bean which will hold pre and post image of record being inserted, updated.

Audit.java


package com.mumz.test.audit.beans;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import com.mumz.test.audit.interfaces.IEntity;

/**
 * The Class Auditable.
 * 
 * @author prabhat.jha
 */
@Entity
@Table(name="AUDIT")
public class Audit implements IEntity {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 6161413362358931496L;

	/** The id. */
	private Long id = null;
	
	/** The pre image. */
	private String preImage = null;

	/** The post image. */
	private String postImage = null;
	
	/** The operation. */
	private String operation = null;

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name="AUDIT_ID")
	public Long getId() {
		return id;
	}

	/**
	 * Sets the id.
	 * 
	 * @param id
	 *            the new id
	 */
	public void setId(Long id) {
		this.id = id;
	}

	/**
	 * Sets the pre image.
	 * 
	 * @param preImage
	 *            the new pre image
	 */
	public void setPreImage(String preImage) {
		this.preImage = preImage;
	}
	
	/**
	 * Gets the pre image.
	 * 
	 * @return the pre image
	 */
	@Column(name="AUDIT_PRE_IMAGE")
	public String getPreImage() {
		return this.preImage;
	}

	/**
	 * Gets the post image.
	 * 
	 * @return the post image
	 */
	@Column(name="AUDIT_POST_IMAGE")
	public String getPostImage() {
		return this.postImage;
	}

	/**
	 * Sets the post image.
	 * 
	 * @param postImage
	 *            the new post image
	 */
	public void setPostImage(String postImage) {
		this.postImage = postImage;
	}
	
	/**
	 * Sets the operation.
	 * 
	 * @param operation
	 *            the new operation
	 */
	public void setOperation(String operation) {
		this.operation = operation;
	}

	/**
	 * Gets the operation.
	 * 
	 * @return the operation
	 */
	@Column(name="AUDIT_OPERATION")
	public String getOperation() {
		return this.operation;
	}

	/** (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((operation == null) ? 0 : operation.hashCode());
		result = prime * result
				+ ((postImage == null) ? 0 : postImage.hashCode());
		result = prime * result
				+ ((preImage == null) ? 0 : preImage.hashCode());
		return result;
	}

	/** (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof Audit)) {
			return false;
		}
		Audit other = (Audit) obj;
		if (operation == null) {
			if (other.operation != null) {
				return false;
			}
		} else if (!operation.equals(other.operation)) {
			return false;
		}
		if (postImage == null) {
			if (other.postImage != null) {
				return false;
			}
		} else if (!postImage.equals(other.postImage)) {
			return false;
		}
		if (preImage == null) {
			if (other.preImage != null) {
				return false;
			}
		} else if (!preImage.equals(other.preImage)) {
			return false;
		}
		return true;
	}

	/** (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "Auditable [preImage=" + preImage + ", postImage=" + postImage
				+ ", operation=" + operation + "]";
	}
}

Since we are trying to build a framework, all actions should be routed through a controller, hence we will build an interface IAuditController and implementation class AuditControllerImpl.

IAuditController


package com.mumz.test.audit.controller;

import java.util.List;

import com.mumz.test.audit.beans.Book;
import com.mumz.test.audit.beans.BookShelf;

/**
 * The Interface IAuditController.
 * @author prabhat.jha
 */
public interface IAuditController {
	
	/**
	 * Adds the book.
	 * 
	 * @param book
	 *            the book
	 * @return the book
	 */
	public Book addBook(Book book);
	
	/**
	 * Update book.
	 * 
	 * @param book
	 *            the book
	 * @return the book
	 */
	public Book updateBook(Book book);
	
	/**
	 * Removes the book.
	 * 
	 * @param book
	 *            the book
	 * @return the book
	 */
	public Book removeBook(Book book);
	
	/**
	 * Fetch all books.
	 * 
	 * @return the list
	 */
	public List<Book> fetchAllBooks();
	
	/**
	 * Adds the book shelf.
	 * 
	 * @param bookShelf
	 *            the book shelf
	 * @return the book shelf
	 */
	public BookShelf addBookShelf(BookShelf bookShelf);
	
	/**
	 * Update book shelf.
	 * 
	 * @param bookShelf
	 *            the book shelf
	 * @return the book shelf
	 */
	public BookShelf updateBookShelf(BookShelf bookShelf);
	
	/**
	 * Removes the book shelf.
	 * 
	 * @param bookShelf
	 *            the book shelf
	 * @return the book shelf
	 */
	public BookShelf removeBookShelf(BookShelf bookShelf);
	
	/**
	 * Fetch all book shelves.
	 * 
	 * @return the list
	 */
	public List<BookShelf> fetchAllBookShelves();
}

AuditControllerImpl


package com.mumz.test.audit.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;

import com.mumz.test.audit.beans.Book;
import com.mumz.test.audit.beans.BookShelf;
import com.mumz.test.audit.dao.IAuditPersistableService;
import com.mumz.test.audit.dao.IAuditQueryService;

/**
 * The Class AuditController.
 * 
 * @author prabhat.jha
 */
@Controller(value="auditControllerImpl")
public class AuditControllerImpl implements IAuditController {

	/** The book dao service. */
	@Autowired
	private IAuditPersistableService auditPersistableService = null;

	/** The audit query service. */
	@Autowired
	private IAuditQueryService auditQueryService = null;

	/**
	 * Gets the audit persistable service.
	 * 
	 * @return the audit persistable service
	 */
	public IAuditPersistableService getAuditPersistableService() {
		return auditPersistableService;
	}

	/**
	 * Sets the audit persistable service.
	 * 
	 * @param auditPersistableService
	 *            the new audit persistable service
	 */
	public void setAuditPersistableService(
			IAuditPersistableService auditPersistableService) {
		this.auditPersistableService = auditPersistableService;
	}

	/**
	 * Gets the audit query service.
	 * 
	 * @return the audit query service
	 */
	public IAuditQueryService getAuditQueryService() {
		return auditQueryService;
	}

	/**
	 * Sets the audit query service.
	 * 
	 * @param auditQueryService
	 *            the new audit query service
	 */
	public void setAuditQueryService(IAuditQueryService auditQueryService) {
		this.auditQueryService = auditQueryService;
	}

       /**
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.mumz.test.audit.controller.IAuditController#addBook(com.mumz.test.
	 * audit.beans.Book)
	 */
	@Override
	public Book addBook(Book book) {
		return this.getAuditPersistableService().addEntity(book);
	}

       /** (non-Javadoc)
	 * @see com.mumz.test.audit.controller.IAuditController#updateBook(com.mumz.test.audit.beans.Book)
	 */
	@Override
	public Book updateBook(Book book) {
		return this.getAuditPersistableService().updateEntity(book);
	}

       /**
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.mumz.test.audit.controller.IAuditController#removeBook(com.mumz.test
	 * .audit.beans.Book)
	 */
	@Override
	public Book removeBook(Book book) {
		return this.getAuditPersistableService().removeEntity(book);
	}

       /**
	 * (non-Javadoc)
	 * 
	 * @see com.mumz.test.audit.controller.IAuditController#fetchAllBooks()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<Book> fetchAllBooks() {
		return (List<Book>) this.getAuditQueryService().fetchAllBooks();
	}

       /**
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.mumz.test.audit.controller.IAuditController#addBookShelf(com.mumz.
	 * test.audit.beans.BookShelf)
	 */
	@Override
	public BookShelf addBookShelf(BookShelf bookShelf) {
		return this.getAuditPersistableService().addEntity(bookShelf);
	}

       /** (non-Javadoc)
	 * @see com.mumz.test.audit.controller.IAuditController#updateBookShelf(com.mumz.test.audit.beans.BookShelf)
	 */
	public BookShelf updateBookShelf(BookShelf bookShelf) {
		return this.getAuditPersistableService().updateEntity(bookShelf);
	}

       /**
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.mumz.test.audit.controller.IAuditController#removeBookShelf(com.mumz
	 * .test.audit.beans.BookShelf)
	 */
	@Override
	public BookShelf removeBookShelf(BookShelf bookShelf) {
		return this.getAuditPersistableService().removeEntity(bookShelf);
	}

       /**
	 * (non-Javadoc)
	 * 
	 * @see com.mumz.test.audit.controller.IAuditController#fetchAllBookShelves()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<BookShelf> fetchAllBookShelves() {
		return (List<BookShelf>) this.getAuditQueryService()
				.fetchAllBookShelves();
	}
}

Next we will implement our dao layer. IAuditPersistableService will be used when we are doing any add or update where as IAuditQueryService will be used fetching records.

IAuditPersistableService.java


package com.mumz.test.audit.dao;

import com.mumz.test.audit.interfaces.IEntity;

/**
 * The Interface IAuditPersistableService.
 * 
 * @author prabhat.jha
 */
public interface IAuditPersistableService {

	/**
	 * Adds the book.
	 * 
	 * @param entityToBeAdded
	 *            the book
	 * @return the book
	 */
	public <T extends IEntity> T addEntity(T entityToBeAdded);

	/**
	 * Update book.
	 * 
	 * @param entityToBeUpdated
	 *            the book
	 * @return the book
	 */
	public <T extends IEntity> T updateEntity(T entityToBeUpdated);

	/**
	 * Removes the book.
	 * 
	 * @param entityToBeRemoved
	 *            the book
	 * @return the book
	 */
	public <T extends IEntity> T removeEntity(T entityToBeRemoved);

}

AuditPersistableServiceImpl.java


package com.mumz.test.audit.dao;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.mumz.test.audit.interfaces.IEntity;

/**
 * The Class AuditPersistableServiceImpl.
 * @author prabhat.jha
 */
@Transactional
@Service
public class AuditPersistableServiceImpl implements IAuditPersistableService {

	/** The entity manager. */
	@PersistenceContext
	private EntityManager entityManager = null;

	/**
	 * Gets the entity manager.
	 *
	 * @return the entity manager
	 */
	public EntityManager getEntityManager() {
		return entityManager;
	}

	/**
	 * Sets the entity manager.
	 *
	 * @param entityManager the new entity manager
	 */
	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}
	
	/** (non-Javadoc)
	 * @see com.mumz.test.audit.dao.IAuditPersistableService#addBook(com.mumz.test.audit.beans.Book)
	 */
	@Override
	public <T extends IEntity> T addEntity(T entityToBeAdded) {
		return this.getEntityManager().merge(entityToBeAdded);
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.dao.IAuditPersistableService#removeBook(com.mumz.test.audit.beans.Book)
	 */
	@Override
	public <T extends IEntity> T removeEntity(T entityToBeRemoved) {
		//Un-tested code, I think JPA doesn't allow direct remove, first a fetch and then subsequent remove is required.
                this.getEntityManager().remove(entityToBeRemoved);
		return entityToBeRemoved;
	}


	/** (non-Javadoc)
	 * @see com.mumz.test.audit.dao.IAuditPersistableService#updateBook(com.mumz.test.audit.beans.Book)
	 */
	@Override
	public <T extends IEntity> T updateEntity(T entityToBeUpdated) {
		return this.getEntityManager().merge(entityToBeUpdated);
	}
}

IAuditQueryService


package com.mumz.test.audit.dao;

import java.util.List;

import com.mumz.test.audit.interfaces.IEntity;

/**
 * The Interface IAuditQueryService.
 * @author prabhat.jha
 */
public interface IAuditQueryService {
	
	/**
	 * Fetch all books.
	 * 
	 * @return the list
	 */
	public List<? extends IEntity> fetchAllBooks();
	
	/**
	 * Fetch all book shelves.
	 * 
	 * @return the list
	 */
	public List<? extends IEntity> fetchAllBookShelves();
}

AuditQueryServiceImpl.java


package com.mumz.test.audit.dao;

import java.util.Collections;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.mumz.test.audit.beans.Book;
import com.mumz.test.audit.beans.BookShelf;

/**
 * The Class AuditQueryServiceImpl.
 * @author prabhat.jha
 */
@Transactional(readOnly = true)
@Service
public class AuditQueryServiceImpl implements IAuditQueryService {

	/** The entity manager. */
	@PersistenceContext
	private EntityManager entityManager = null;
	
	/**
	 * Gets the entity manager.
	 * 
	 * @return the entity manager
	 */
	public EntityManager getEntityManager() {
		return entityManager;
	}

	/**
	 * Sets the entity manager.
	 * 
	 * @param entityManager
	 *            the new entity manager
	 */
	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.dao.IAuditPersistableService#fetchAllBooks()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<Book> fetchAllBooks() {
		try {
			Query namedQuery = this.getEntityManager().createNamedQuery("fetchAllBooks");
			return namedQuery.getResultList();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return Collections.emptyList();
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.dao.IAuditPersistableService#fetchAllBookShelves()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<BookShelf> fetchAllBookShelves() {
		try {
			Query namedQuery = this.getEntityManager().createNamedQuery("fetchAllBookShelves");
			return namedQuery.getResultList();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}

Next is our Audit Advice which actually does the audit entry.

AuditAdvice.java


package com.mumz.test.audit.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.mumz.test.audit.beans.Audit;
import com.mumz.test.audit.dao.IAuditPersistableService;
import com.mumz.test.audit.interfaces.IAuditable;
import com.mumz.test.audit.interfaces.IXMLConvertable;
import com.mumz.test.audit.utils.XStreamUtils;

/**
 * The Class BookAdvice.
 * 
 * @author prabhat.jha
 */
@Aspect
@Component
public class AuditAdvice {

	/** The audit persistable service. */
	@Autowired
	private IAuditPersistableService auditPersistableService = null;

	/**
	 * Gets the audit persistable service.
	 * 
	 * @return the audit persistable service
	 */
	public IAuditPersistableService getAuditPersistableService() {
		return auditPersistableService;
	}

	/**
	 * Sets the audit persistable service.
	 * 
	 * @param auditPersistableService
	 *            the new audit persistable service
	 */
	public void setAuditPersistableService(
			IAuditPersistableService auditPersistableService) {
		this.auditPersistableService = auditPersistableService;
	}

	/**
	 * Around remove advice.
	 * 
	 * @param pjp
	 *            the pjp
	 */
	@Around("execution(* com.mumz.test.audit.controller.AuditControllerImpl.*(..))")
	public void aroundAddBookShelfAdvice(ProceedingJoinPoint pjp) {
		String methodName = pjp.getSignature().getName();
		String operation = null;
		if (methodName.toUpperCase().contains("ADD")) {
			operation = IAuditable.OPERATION_INSERT;
		} else if (methodName.toUpperCase().contains("UPDATE")) {
			operation = IAuditable.OPERATION_UPDATE;
		} else {
			operation = IAuditable.OPERATION_DELETE;
		}
		Object[] arguments = pjp.getArgs();
		IXMLConvertable preImage = null;
		IXMLConvertable postImage = null;
		for (Object object : arguments) {
			if (!operation.equalsIgnoreCase(IAuditable.OPERATION_INSERT)
					&& object instanceof IAuditable
					&& object instanceof IXMLConvertable) {
				preImage = (IXMLConvertable) object;
			}
		}
		try {
			Object returnValue = pjp.proceed();
			if (operation.equalsIgnoreCase(IAuditable.OPERATION_UPDATE)
					&& returnValue instanceof IAuditable
					&& returnValue instanceof IXMLConvertable) {
				postImage = (IXMLConvertable) returnValue;
			}
		} catch (Throwable e) {
			e.printStackTrace();
		}
		if (preImage != null || postImage != null) {
			Audit auditableEntity = new Audit();
			auditableEntity.setOperation(IAuditable.OPERATION_INSERT);
			auditableEntity.setPreImage(XStreamUtils.getXMLFromObject(preImage,
					preImage.getClass().getName(), preImage.getFieldAliases(),
					preImage.getFieldsToBeOmitted()));
			auditableEntity.setPostImage(XStreamUtils.getXMLFromObject(
					postImage, postImage.getClass().getName(),
					postImage.getFieldAliases(),
					postImage.getFieldsToBeOmitted()));
			this.getAuditPersistableService().addEntity(auditableEntity);
		}
	}
}

Next is our XStreamUtils which is a generic code and converts given IXMLConvertable object and returns a XML representation of it.

XStreamUtils.java


package com.mumz.test.audit.utils;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

/**
 * The Class XStreamUtils.
 * 
 * @author prabhat.jha
 */
public class XStreamUtils {

	/**
	 * Gets the xML from object.
	 * 
	 * @param toBeConverted
	 *            the to be converted
	 * @param classNameAlias
	 *            the class name alias
	 * @param fieldAlias
	 *            the field alias
	 * @param fieldsToBeOmitted
	 *            the fields to be omitted
	 * @return the xML from object
	 */
	public static String getXMLFromObject(Object toBeConverted,
			String classNameAlias, Map<String, String> fieldAlias,
			List<String> fieldsToBeOmitted) {
		StringBuilder objectAsXML = new StringBuilder();
		if (toBeConverted != null) {
			XStream xStream = new XStream(new DomDriver());
			if (StringUtils.isNotEmpty(classNameAlias)) {
				xStream.alias(classNameAlias, toBeConverted.getClass());
			}
			if (fieldAlias != null && !fieldAlias.isEmpty()) {
				for (Entry<String, String> entry : fieldAlias.entrySet()) {
					xStream.aliasField(entry.getKey(),
							toBeConverted.getClass(), entry.getValue());
				}
			}
			if (CollectionUtils.isNotEmpty(fieldsToBeOmitted)) {
				for (String fieldToBeOmitted : fieldsToBeOmitted) {
					xStream.omitField(toBeConverted.getClass(),
							fieldToBeOmitted);
				}
			}
			objectAsXML.append(xStream.toXML(toBeConverted));
		}
		return objectAsXML.toString();
	}
}

Next is our spring configuration file.

audit-bean-definition.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation=
		"http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
		http://www.springframework.org/schema/tx 
		http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
    	http://www.springframework.org/schema/aop 
    	http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
    	http://www.springframework.org/schema/context
    	http://www.springframework.org/schema/context/spring-context-3.1.xsd">

	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="location">
			<value>jdbc.properties</value>
		</property>
	</bean>

	<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
	
	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName">
			<value>${jdbc.driverClassName}</value>
		</property>
		<property name="url">
			<value>${jdbc.url}</value>
		</property>
		<property name="username">
			<value>${jdbc.username}</value>
		</property>
		<property name="password">
			<value>${jdbc.password}</value>
		</property>
	</bean>
	
	<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="jpaVendorAdapter">
			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
				<property name="database">
					<value>${jdbc.databaseVendor}</value>
				</property>
				<property name="showSql" value="false" />
			</bean>
		</property>
	</bean>
	
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>

	<tx:annotation-driven transaction-manager="transactionManager" />
	
	<context:component-scan base-package="com.mumz.test.audit"></context:component-scan>
	
	<context:annotation-config></context:annotation-config>
	
	<aop:aspectj-autoproxy/>
</beans>

I have placed all the database properties inside jdbc.properties file

jdbc.properties


jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc\:mysql\://localhost\:3306/AuditSchema
jdbc.username=root
jdbc.password=root
hibernate.dialect=org.hibernate.dialect.MySQLDialect
jdbc.databaseVendor=MYSQL

JPA needs persistence.xml inside META-INF folder in the classpath, so we will create our persistence.xml.

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
	<persistence-unit name="auditPersitenceUnit">
		<class>com.mumz.test.audit.beans.Book</class>
		<class>com.mumz.test.audit.beans.BookShelf</class>
		<class>com.mumz.test.audit.beans.Audit</class>
	</persistence-unit>
</persistence>

Finally some test code to check our implementation.

AuditMainApp.java


package com.mumz.test.audit.app;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.mumz.test.audit.beans.Book;
import com.mumz.test.audit.beans.BookShelf;
import com.mumz.test.audit.interfaces.IAuditController;

public class AuditMainApp {
	public static void main(String[] args) {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("audit-bean-definition.xml");
		IAuditController auditController = applicationContext.getBean(IAuditController.class);
		BookShelf bookShelf = new BookShelf();
		Book book  = new Book();
		book.setAuthor("Test Author 1");
		book.setName("Test Book 1");
		book.setBookShelf(bookShelf);
		List<Book> books = new ArrayList<Book>();
		books.add(book);
		bookShelf.setBooks(books);
		bookShelf.setName("Test 1");
		auditController.addBookShelf(bookShelf);
	}
}

Database schema used for this post is below.

Database Schema


SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL';

DROP SCHEMA IF EXISTS `auditschema` ;
CREATE SCHEMA IF NOT EXISTS `auditschema` DEFAULT CHARACTER SET utf8 ;
USE `auditschema` ;

-- -----------------------------------------------------
-- Table `auditschema`.`audit`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `auditschema`.`audit` ;

CREATE  TABLE IF NOT EXISTS `auditschema`.`audit` (
  `AUDIT_ID` INT(11) NOT NULL AUTO_INCREMENT ,
  `AUDIT_PRE_IMAGE` VARCHAR(3000) NULL DEFAULT NULL ,
  `AUDIT_POST_IMAGE` VARCHAR(3000) NULL DEFAULT NULL ,
  `AUDIT_OPERATION` VARCHAR(45) NULL DEFAULT NULL ,
  PRIMARY KEY (`AUDIT_ID`) )
ENGINE = InnoDB
AUTO_INCREMENT = 2
DEFAULT CHARACTER SET = utf8;

-- -----------------------------------------------------
-- Table `auditschema`.`book_shelf`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `auditschema`.`book_shelf` ;

CREATE  TABLE IF NOT EXISTS `auditschema`.`book_shelf` (
  `BOOK_SHELF_ID` INT(11) NOT NULL AUTO_INCREMENT ,
  `BOOK_SHELF_NAME` VARCHAR(45) NOT NULL ,
  PRIMARY KEY (`BOOK_SHELF_ID`) )
ENGINE = InnoDB
AUTO_INCREMENT = 10
DEFAULT CHARACTER SET = utf8;

-- -----------------------------------------------------
-- Table `auditschema`.`book`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `auditschema`.`book` ;

CREATE  TABLE IF NOT EXISTS `auditschema`.`book` (
  `BOOK_ID` INT(11) NOT NULL AUTO_INCREMENT ,
  `BOOK_NAME` VARCHAR(45) NOT NULL ,
  `BOOK_AUTHOR` VARCHAR(45) NOT NULL ,
  `BOOK_SHELF_ID` INT(11) NOT NULL ,
  PRIMARY KEY (`BOOK_ID`) ,
  INDEX `FK_BOOK_SHELF_ID` (`BOOK_SHELF_ID` ASC) ,
  CONSTRAINT `FK_BOOK_SHELF_ID`
    FOREIGN KEY (`BOOK_SHELF_ID` )
    REFERENCES `auditschema`.`book_shelf` (`BOOK_SHELF_ID` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB
AUTO_INCREMENT = 9
DEFAULT CHARACTER SET = utf8;

SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

If you have followed every step your code structure should look similar to:
Project Structure

That's all, It has been a very long post, I hope you find it useful.

Accessing method arguments using Spring AOP

Posted on Updated on

Earlier we used Spring AOP to weave common logging functionality to our BookShelf add and remove methods. But those were plain log statements, what if we want to log which Book is being added/removed. Spring AOP provides @Around advice which can be used to get this use case working.

First we will enhance our Book.java and provide implementation for toString, so that we can log some valuable information.

Book.java


package com.mumz.test.spring.aop;

/**
 * The Class Book.
 * 
 * @author prabhat.jha
 */
public class Book {

	/** The name. */
	private String name = null;

	/** The author. */
	private String author = null;

	/** The publisher. */
	private String publisher = null;

	/**
	 * The Constructor.
	 */
	public Book() {

	}

	/**
	 * Gets the name.
	 * 
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Sets the name.
	 * 
	 * @param name
	 *            the name
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Gets the author.
	 * 
	 * @return the author
	 */
	public String getAuthor() {
		return author;
	}

	/**
	 * Sets the author.
	 * 
	 * @param author
	 *            the author
	 */
	public void setAuthor(String author) {
		this.author = author;
	}

	/**
	 * Gets the publisher.
	 * 
	 * @return the publisher
	 */
	public String getPublisher() {
		return publisher;
	}

	/**
	 * Sets the publisher.
	 * 
	 * @param publisher
	 *            the publisher
	 */
	public void setPublisher(String publisher) {
		this.publisher = publisher;
	}

	/**
	 * (non-Javadoc).
	 * 
	 * @return the int
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((author == null) ? 0 : author.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result
				+ ((publisher == null) ? 0 : publisher.hashCode());
		return result;
	}

	/**
	 * (non-Javadoc).
	 * 
	 * @param obj
	 *            the obj
	 * @return true, if successful
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof Book)) {
			return false;
		}
		Book other = (Book) obj;
		if (author == null) {
			if (other.author != null) {
				return false;
			}
		} else if (!author.equals(other.author)) {
			return false;
		}
		if (name == null) {
			if (other.name != null) {
				return false;
			}
		} else if (!name.equals(other.name)) {
			return false;
		}
		if (publisher == null) {
			if (other.publisher != null) {
				return false;
			}
		} else if (!publisher.equals(other.publisher)) {
			return false;
		}
		return true;
	}

	/** (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "Book [name=" + name + ", author=" + author + ", publisher="
				+ publisher + "]";
	}
}

Second we will enhance our BookAdvice.java and introduce @Around advice.

BookAdvice

package com.mumz.test.spring.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * The Class BookAdvice.
 * @author prabhat.jha
 */
@Aspect
public class BookAdvice {
	
	/**
	 * Before add advice.
	 */
	@Before(value="execution(* com.mumz.test.spring.aop.BookShelf.addBook(..))")
	public void beforeAddAdvice(){
		System.out.println("Before adding new book in the list");
	}
	
	/**
	 * After add advice.
	 */
	@After("execution(* com.mumz.test.spring.aop.BookShelf.addBook(..))")
	public void afterAddAdvice(){
		System.out.println("After adding new book in the list");
	}
	
	/**
	 * Before remove advice.
	 */
	@Before("execution(* com.mumz.test.spring.aop.BookShelf.removeBook(..))")
	public void beforeRemoveAdvice(){
		System.out.println("Before adding new book in the list");
	}
	
	/**
	 * After remove advice.
	 */
	@After("execution(* com.mumz.test.spring.aop.BookShelf.removeBook(..))")
	public void afterRemoveAdvice(){
		System.out.println("After adding new book in the list");
	}
	
	/**
	 * Around add advice.
	 * 
	 * @param pjp
	 *            the pjp
	 */
	@Around("execution(* com.mumz.test.spring.aop.BookShelf.addBook(..))")
	public void aroundAddAdvice(ProceedingJoinPoint pjp){
		Object[] arguments = pjp.getArgs();
		for (Object object : arguments) {
			System.out.println("Book being added is : " + object);
		}
		try {
			pjp.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Around remove advice.
	 * 
	 * @param pjp
	 *            the pjp
	 */
	@Around("execution(* com.mumz.test.spring.aop.BookShelf.removeBook(..))")
	public void aroundRemoveAdvice(ProceedingJoinPoint pjp){
		Object[] arguments = pjp.getArgs();
		for (Object object : arguments) {
			System.out.println("Book being removed is : " + object);
		}
		try {
			pjp.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}
}

All other code remains the same from previous post.

Spring AOP using Annotation

Posted on Updated on

AOP a.k.a Aspect Oriented programming using Spring is a common usage now. In a nutshell it provides common functionality to be weaved into beans. Functionalities like logging before and after method is executed instead of being part of actual method implementation can be wired.

Some important notes about AOP with Spring:

  1. Spring AOP is implemented at Runtime and not compile time
  2. Spring only supports method level AOP so if you want to achieve AOP say when a constructor is called, look towards other implementation like AspectJ

Now there are few jargon about AOP which one should be familiar of, I will try to keep them one-liner

  1. Advice: Code which you want to run (common functionality)
  2. Join Point: Where all you want to run advice
  3. Point Cut: Of all the join point where exactly to run advice
  4. Aspect: What to apply and where to apply (Advice + Point Cut)

Enough of theory let’s make it clear by writing some code:

First let’s setup maven and define our dependencies (Need help creating Java Project using Maven in Eclipse ?)

pom.xml

<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>TestSpring</groupId>
	<artifactId>TestSpring</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<repositories>
		<repository>
			<id>central</id>
			<name>release</name>
			<url>http://repo.springsource.org/release</url>
		</repository>
	</repositories>
	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>3.1.2.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>3.1.2.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>3.1.2.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
			<version>2.2</version>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>1.6.11</version>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.6.11</version>
		</dependency>
	</dependencies>
</project>

Second let’s write our bean classes. Our use will have BookShelf with method to add and remove Book and Book will have few properties like name, author, publisher etc.

Book.java


package com.mumz.test.spring.aop;

/**
 * The Class Book.
 * 
 * @author prabhat.jha
 */
public class Book {

	/** The name. */
	private String name = null;

	/** The author. */
	private String author = null;

	/** The publisher. */
	private String publisher = null;

	/**
	 * The Constructor.
	 */
	public Book() {

	}

	/**
	 * Gets the name.
	 * 
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Sets the name.
	 * 
	 * @param name
	 *            the name
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Gets the author.
	 * 
	 * @return the author
	 */
	public String getAuthor() {
		return author;
	}

	/**
	 * Sets the author.
	 * 
	 * @param author
	 *            the author
	 */
	public void setAuthor(String author) {
		this.author = author;
	}

	/**
	 * Gets the publisher.
	 * 
	 * @return the publisher
	 */
	public String getPublisher() {
		return publisher;
	}

	/**
	 * Sets the publisher.
	 * 
	 * @param publisher
	 *            the publisher
	 */
	public void setPublisher(String publisher) {
		this.publisher = publisher;
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((author == null) ? 0 : author.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result
				+ ((publisher == null) ? 0 : publisher.hashCode());
		return result;
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof Book)) {
			return false;
		}
		Book other = (Book) obj;
		if (author == null) {
			if (other.author != null) {
				return false;
			}
		} else if (!author.equals(other.author)) {
			return false;
		}
		if (name == null) {
			if (other.name != null) {
				return false;
			}
		} else if (!name.equals(other.name)) {
			return false;
		}
		if (publisher == null) {
			if (other.publisher != null) {
				return false;
			}
		} else if (!publisher.equals(other.publisher)) {
			return false;
		}
		return true;
	}
}

Third our BookShelf.java.


package com.mumz.test.spring.aop;

import java.util.ArrayList;
import java.util.List;

/**
 * The Class BookShelf.
 * @author prabhat.jha
 */
public class BookShelf {
	
	/** The books. */
	private List<Book> books = null;
	
	/**
	 * The Constructor.
	 */
	public BookShelf(){
		this.books = new ArrayList<Book>();
	}
	
	/**
	 * Adds the book.
	 * 
	 * @param book
	 *            the book
	 * @return true, if adds the book
	 */
	public boolean addBook(Book book){
		return this.books.add(book);
	}
	
	/**
	 * Removes the book.
	 * 
	 * @param book
	 *            the book
	 * @return true, if removes the book
	 */
	public boolean removeBook(Book book){
		return this.books.remove(book);
	}
}

Both these classes are very simple and don’t have much functionality. Now we will use AOP and add log statement when ever addBook and removeBook methods are invoked.

Fourth we will implement our Advice (functionality which needs to be weaved).

BookAdvice

package com.mumz.test.spring.aop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * The Class BookAdvice.
 * @author prabhat.jha
 */
@Aspect
public class BookAdvice {
	
	/**
	 * Before add advice.
	 */
	@Before(value="execution(* com.mumz.test.spring.aop.BookShelf.addBook(..))")
	public void beforeAddAdvice(){
		System.out.println("Before adding new book in the list");
	}
	
	/**
	 * After add advice.
	 */
	@After("execution(* com.mumz.test.spring.aop.BookShelf.addBook(..))")
	public void afterAddAdvice(){
		System.out.println("After adding new book in the list");
	}
	
	/**
	 * Before remove advice.
	 */
	@Before("execution(* com.mumz.test.spring.aop.BookShelf.removeBook(..))")
	public void beforeRemoveAdvice(){
		System.out.println("Before adding new book in the list");
	}
	
	/**
	 * After remove advice.
	 */
	@After("execution(* com.mumz.test.spring.aop.BookShelf.removeBook(..))")
	public void afterRemoveAdvice(){
		System.out.println("After adding new book in the list");
	}
}

Important things to note in above code:

  1. @Aspect : This makes this class an aspect
  2. @Before : Pointcut (when it has to be invocked, so this says before method is executed)
  3. @After : Pointcut (when it has to be invocked, so this says after method is executed)
  4. execution(“* a.b.c(..)” : when method c of class b is executed which returns anything and takes variable arguments

Fifth our configuration file.

springconfiguration.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation=
		"http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context-3.1.xsd
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
	<bean id="bookShelf" class="com.mumz.test.spring.aop.BookShelf" scope="prototype"></bean>
	<bean id="book" class="com.mumz.test.spring.aop.Book" scope="prototype"></bean>
	<bean id="bookAdvice" class="com.mumz.test.spring.aop.BookAdvice"></bean>
	<aop:aspectj-autoproxy/>
</beans>

Final some test code to create and see AOP in action.

AopMainApp.java


package com.mumz.test.spring.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * The Class AopMainApp.
 * @author prabhat.jha
 */
public class AopMainApp {
	
	/**
	 * The main method.
	 * 
	 * @param args
	 *            the args
	 */
	public static void main(String[] args) {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("springconfiguration.xml");
		BookShelf bookShelf = applicationContext.getBean(BookShelf.class);
		Book book  = applicationContext.getBean(Book.class);
		book.setAuthor("Test Author");
		book.setName("Test Book");
		book.setPublisher("Test Publisher");
		bookShelf.addBook(book);
		bookShelf.removeBook(book);
	}
}

That’s all, hope this is simple enough.