Getting into Spring AOP – Implementing simple business logic on top of Domain Objects using Aspect Oriented Programming

5

Aspect Oriented Programming is one of the hypes in Java programming. Through AOP, we can program the functionality for a Class in more ways than just by writing the java code for the Class itself and maybe extending a super class. Aspects can represent additional dimensions of functionality, that are combined with the logic coded in a Class itself. Classes can be advised with Aspects, that means that they are somehow enahanced with functionality programmed in the aspects. By the way, Aspects are classes themselves. No new fancy Java constructs are introduced, it is all pretty plain Java. See for a more detailed introduction on Spring AOP my previous post Spring Workshop – Day Four – Rod and Alef on AOP, JMX and Remoting (Spring does EJB??).

From this post:

once you get your head wrapped round the concept of Aspects and the use of one or even multiple orthogonal dimensions to provide behavior to your classes, it becomes very enticing. Some developers can focus on the primary (business) functionality of classes, while others, almost independently, can enhance these classes with generic, infrastructural behavior, that can add transactional control, profiling, auditing etc. to the domain objects. This clearly leads to a whole new way of designing the objects as well as organizing the development effort. Fortunately, again we can start small, adding just a single, low level aspect to see how it goes, gradually increasing the amount of functionality we trust to have outside our classes. Initially it can be a quite scary thought to see your objects doing things that were never programmed into them!

To introduce some basic concepts of Spring AOP, I will show the following in this post: my application contains a domain interface Employee. I also have a Domain Class that implements this Interface, EmployeeImpl. The interfaces specifies getter and setter methods for properties such as FirstName, LastName, Salary and JobTitle.

My application does not use a BeanFactory or the idea of Inversion of Control -which is another important concept in the Spring Framework. I want to keep the example simple, with minimal dependencies . To that end, the application gets hold of Employee objects by simply instantiating EmployeeImpl objects, using new EmployeeImpl(). Note that this does not represent good practice!

Now we come to reason I need AOP in this case: the Domain Object does not contain any Business Logic! The implementor wrote the class and left. She forgot to do anything about invalid conditions that could arise when consumers of the Domain Objects start setting values. We need to have the domain enforce rules such as: “A Salesman may not earn more than 5000″ and “John Doe is not an acceptable name for an employee”. Let’s assume that we have received the Domain Classes in a JAR; we do not have any source code. We could make use of a subclass that overrides setter-methods, performs the validation and then calls the superclass’s setter method. AOP provides an alternative approach, one that is particularly useful if the behavior that we want to add to a class will be reused.

With AOP, we make a small change to the application. Instead of getting hold of an EmployeeImpl object, we have a Spring ProxyFactory intervene and wrap the EmployeeImpl in a proxy object. We let the proxy intercept calls to setter methods, verify their validity and throw an exception for incorrect values. When the validation succeeds, the setter method on the EmployeeImpl object is invoked as we intended all along.

For this, we need a class that implements the Spring MethodBeforeAdvice interface. We can inject this class into the ProxyFactory; this instructs the ProxyFactory to intercept any call to methods on the EmployeeImpl object and call the before() method on the MethodBeforeAdvice before continuing with the original call to the EmployeeImpl method.

So in short, the steps are:

  1. Specify the Employee “domain interface”
  2. Acquire implementation of the Employee Interface (EmployeeImpl)
  3. Create an implementation of the MethodBeforeAdvice interface that handles Validation of Business Rules in its before method
  4. Have the application invoke the Spring AOP ProxyFactory to return a proxy wrapping the Employee instance, after instructing the ProxyFactorythat the MethodBeforeAdvice should be applied to the proxy
  5. From the application, Invoke the getters and setters on the Employee instance – the proxied EmployeeImpl

The Employee Interface and the EmployeeImpl Class

First of all, the interface for the Domain entity Employee:

package nl.amis.spring.trial;
import java.util.Date;
public interface Employee  {
  public String getFirstName();
  public void setFirstName(String FirstName);
  public String getLastName();
  public void setLastName(String LastName);
  public String getJobTitle();
  public void setJobTitle(String JobTitle);
  public Float getSalary();
  public void setSalary(Float Salary);
  public Date getHiredate();
  public void setHiredate(Date Hiredate);

}

Then the basic implementation (although we do not actually need the source for it, Spring AOP works with compiled class files

package nl.amis.spring.trial;
import java.util.Date;
public class EmployeeImpl implements Employee {
  String FirstName;
  String LastName;
  String JobTitle;
  Float Salary;
  Date Hiredate;
  public EmployeeImpl() {
  }
  public String getFirstName() {
    return FirstName;
  }
  public void setFirstName(String FirstName) {
    this.FirstName = FirstName;
  }
  public String getLastName() {
    return LastName;
  }
  public void setLastName(String LastName) {
    this.LastName = LastName;
  }
  public String getJobTitle() {
    return JobTitle;
  }
  public void setJobTitle(String JobTitle) {
    this.JobTitle = JobTitle;
  }
  public Float getSalary() {
    return Salary;
  }
  public void setSalary(Float Salary) {
    this.Salary = Salary;
  }
  public Date getHiredate() {
    return Hiredate;
  }
  public void setHiredate(Date Hiredate) {
    this.Hiredate = Hiredate;
  }
}

Our simple application, without AOP and therefore without Business Logic

package nl.amis.spring.trial;
public class MyFirstAOP {
  public MyFirstAOP() {
  }
  public Employee getEmployee() {
    Employee emp = new EmployeeImpl();
    return emp;
}
  public static void main(String[] args) {
    MyFirstAOP myFirstAOP = new MyFirstAOP();
    Employee scott = myFirstAOP.getEmployee();
    scott.setFirstName("John");
    scott.setLastName("Doe");
    scott.setJobTitle("SALESMAN");
    scott.setSalary(new Float("5000.32"));
    System.out.println("The new salary for "+scott.getFirstName()+" "+scott.getLastName()+" ("+scott.getJobTitle()+") = "+scott.getSalary());
  }
}

The result from running this Class would be:The new salary for John Doe (SALESMAN)= 5000.32
However, we have clearly violated two business rules: we have created an employee called John Doe, and we have a SALESMAN who earns more than 4000. So apparently this is not good!

The MethodBeforeAdvice implementation: EmployeeValidator Class

This class implements the MethodBeforeAdvice interface and can therefore be applied by the ProxyFactory to the proxy for EmployeeImpl:

package nl.amis.spring.trial;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class EmployeeValidator implements MethodBeforeAdvice  {
  public EmployeeValidator() {
  }
  public void before(Method method, Object[] args, Object target) throws Throwable {
    Employee emp = (EmployeeImpl)target;
    if (method.getName().equalsIgnoreCase("setSalary")) {
      if ("SALESMAN".equalsIgnoreCase(emp.getJobTitle())) {
         // if the job of this employee is SALESMAN, he/she may not earn more than 4000
         float newSalary =  ((Float)args[0]).floatValue();
         if (newSalary > 4000) {
           throw new RuntimeException("Salary may not exceed 4000 for Salesmen such as "+emp.getFirstName()+" "+emp.getLastName());
         }
      }
    }
    if (method.getName().equalsIgnoreCase("setFirstName")) {
      if ("Doe".equalsIgnoreCase(emp.getLastName())) {
         // we do not want any employee to be called John Doe
         if ("John".equalsIgnoreCase((String)args[0])) {
           throw new RuntimeException("Employees should not be called John Doe. Choose another First Name please.");
         }
      }
    }
    if (method.getName().equalsIgnoreCase("setLastName")) {
      if ("John".equalsIgnoreCase(emp.getFirstName())) {
         // we do not want any employee to be called John Doe
         if ("Doe".equalsIgnoreCase((String)args[0])) {
           throw new RuntimeException("Employees should not be called John Doe. Choose another Last Name please.");
         }
      }
    }
  }
}

Implementing the application using the Spring AOP Proxy Factory

We can now slightly modify the getEmployee() method in our application, to return a proxy instead of the EmployeeImpl and have the Advice applied to the proxy

  public Employee getEmployee() {
    Employee emp = new EmployeeImpl();
    ProxyFactory pf = new ProxyFactory();
    pf.setTarget(emp);
    pf.setInterfaces(new Class[]{Employee.class}); // this line is required for using the JDK 1.3 proxy based Spring AOP implementation,
                                                                                        //otherwise the CGLib libraries are required
    pf.addAdvice(new EmployeeValidator());
    return (Employee)pf.getProxy();
  }

If we now run our application, the output is the following:

Employees should not be called John Doe. Choose another Last Name please.

Conclusion and next steps

We achieved what we set out to do: apply business logic to our Domain Object. Without messing with the code of the EmployeeImpl, we managed to wrap business logic validations around our domain object. We have seen that we can no longer bring our Employees in states that are not allowed.

We have not used the Spring Container, the BeanFactory that we can declaratively configure. Instead, we have instantiated EmployeeImpl objects ourselves in the getEmployee() method. Here we have created an unnecessary dependency of our application on the actual EmployeeImpl class. Furthermore, we have implemented the Proxy-ing programmatically. When using the container, we can set up all of the proxy definitions in the XML file that configures the container. Our application would not see anything about either EmployeeImpl or the ProxyFactory.

In this example we have seen only the most basic Advice that we can apply through Spring AOP: the MethodBeforeAdvice. Spring AOP supports several others ways of intercepting calls to methods on proxied objects: After, Throws and Around Advice. The most interesting bit of Advice is called Introduction. Introduction advice allows you to specify that a proxy implements Interfaces that the proxied object itself does not implement.

In subsequent posts, I will discuss some examples of these other types of using Spring AOP.

Resources

You can download the JDeveloper workspace for this almost trivial demo of Spring AOP here: SpringTrials.zip. Note: You have to define the Spring Framework library in the project SpringAOP; this library should contain the spring jar-files that you can download below. You must also make sure that your library contains the log4.jar – since Spring relies on Log4J. Alternatively you can download Spring with all dependencies from the link below.
To download the Spring Framework, go to SourceForge

Share.

About Author

Lucas Jellema, active in IT (and with Oracle) since 1994. Oracle ACE Director for Fusion Middleware. Consultant, trainer and instructor on diverse areas including Oracle Database (SQL & PLSQL), Service Oriented Architecture, BPM, ADF, Java in various shapes and forms and many other things. Author of the Oracle Press book: Oracle SOA Suite 11g Handbook. Frequent presenter on conferences such as JavaOne, Oracle OpenWorld, ODTUG Kaleidoscope, Devoxx and OBUG. Presenter for Oracle University Celebrity specials.

5 Comments

  1. Zeger Hendrikse on

    Hi Lucas, all very interesting and clear posts, on both Spring and AOP. I have read them which much interest. I’m looking forward to the follow-ups and the workshop.