Java implements an example tutorial for AOP's section oriented programming

  • 2020-05-09 18:38:24
  • OfStack

introduce

As we all know, AOP (aspect oriented programming) is one of the features of the Spring framework. AOP provides great scalability by setting crosscutting concerns (cross cutting concerns). So how does AOP work in Spring? When you can only use core java, but you need AOP technology, the answer to this question becomes critical. Not only that, in the interview of advanced technical post, this kind of question also often comes up as examination question. Well, a friend of mine recently had an interview and was asked the tough question of how to implement AOP without using Spring and its associated libraries, but only core Java. Therefore, in this article, I will provide an outline to help you understand how to implement an AOP using only core Java (although, of course, this AOP has a definite functional limitation). Note that this article is not a comparative study of Spring AOP versus Java AOP, but a tutorial on implementing AOP in core Java with inherent design patterns.

You already know what AOP is and how to use it in the Spring framework, so this article will focus on how to implement AOP without using Spring. First of all, we need to know that Spring implements AOP with the help of JDK proxy and CGlib. JDK dynamic proxy provides a flexible way to hook1 methods and perform the specified operations, but only with one restriction: you must first provide an associated interface and its implementation class. Practice gives true knowledge, let us pass through 1 case to understand this sentence! Now we have a calculator program that does some math. Let's consider the division function. The question at this point is: if core framework already has a copy of the code that implements division, can we hijack (highjack) it during code execution and perform additional validation? The answer is yes, and I'll prove it with the code snippet provided below. First look at the basic interface code:


public interface Calculator {
  public int calculate( int a , int b);
}

The code of the interface implementation class is as follows:


public class CalculatorImpl implements Calculator {
  @Override
  public int calculate(int a, int b) {
    return a/b;
  }
}

Assuming that we can neither fix the above code nor make any changes to the core library, how can we implement the validation function perfectly? Try JDK dynamic proxy.


public class SomeHandler implements InvocationHandler {
 
// Code omitted for simplicity ... ..
 
  @Override
  public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
// Your complex business validation and logic
    Object result = method.invoke(targetObject ,params);
    return result;
  }
 
}

Let's test the class to see how the validation functionality implemented by JDK dynamic proxy works.


public static void main(String[] args) {
    CalculatorImpl calcImpl = new CalculatorImpl();
    Calculator proxied = (Calculator)ProxyFactory.getProxy (Calculator.class, calcImpl, 
        new SomeHandler(calcImpl));
    int result = proxied.calculate(20, 10);
    System.out.println("FInal Result :::" + result);
  }

As you can see from the results, simply implementing the powerful InvocationHandler interface gives you an hooking implementation. According to the JDK documentation, the InvocationHandler interface handles one method call with one proxy instance (proxy instance).

We now know that InvocationHandler's invoke() method can help us solve the problem. So let's solve a new problem -- how do you perform operations before and after method execution? More specifically, can we hook1 by adding multiple before (before, after, around) to add multiple aops? The answer is yes, too. Follow these steps to create a simplified code template to meet this requirement:

Create an abstract class to apply aop to the target object. Create two aops named BeforeHandler and AfterHandler. The former works before the method is executed, while the latter works after the method is executed. Create a proxy class so that all aop handler and target objects can be passed in as parameters to create an hook. Add your own business logic or crosscutting concerns. Finally, create the proxy object (proxy object) by passing in the relevant parameters.

There are two ways to implement AOP:  

1. JDK provides dynamic proxy implementation of    
Interface  


public interface UserBean 
{ 
  void getUser(); 
  void addUser(); 
  void updateUser(); 
  void deleteUser(); 
} 

The original implementation class  


public class UserBeanImpl implements UserBean 
{ 
  private String user = null; 
  public UserBeanImpl() 
  {     
  }   
  public UserBeanImpl(String user) 
  { 
    this.user = user; 
  }   
  public String getUserName() 
  { 
    return user; 
  }   
  public void getUser() 
  { 
    System.out.println("this is getUser() method!"); 
  } 
 
  public void setUser(String user) 
  { 
    this.user = user; 
    System.out.println("this is setUser() method!"); 
  } 
  public void addUser() 
  { 
    System.out.println("this is addUser() method!"); 
  } 
   
  public void updateUser() 
  { 
    System.out.println("this is updateUser() method!"); 
  }   
  public void deleteUser() 
  { 
    System.out.println("this is deleteUser() method!");  
  }     
} 

Proxy class    


import java.lang.reflect.InvocationHandler; 
import java.lang.reflect.Method; 
import java.lang.reflect.Proxy; 
import com.cignacmc.finance.bean.UserBeanImpl; 
 
public class UserBeanProxy implements InvocationHandler 
{ 
  private Object targetObject; 
   
  public UserBeanProxy(Object targetObject) 
  { 
    this.targetObject = targetObject;     
  } 
   
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
  { 
    UserBeanImpl userBean = (UserBeanImpl) targetObject; 
    String userName = userBean.getUserName(); 
    Object result = null; 
     
    // Authority to judge  
    if(userName != null && !"".equals(userName)) 
    { 
      result = method.invoke(targetObject, args); 
    } 
     
    return result; 
  } 
} 

 
Test class    


import java.lang.reflect.Proxy; 
 
import com.cignacmc.finance.bean.UserBean; 
import com.cignacmc.finance.bean.UserBeanImpl; 
import com.cignacmc.finance.proxy.UserBeanProxy; 
 
public class ProxyExe 
{ 
  public static void main(String[] args) 
  { 
    System.out.println("Proved............."); 
    UserBeanImpl targetObject = new UserBeanImpl("Bob Liang");    
    UserBeanProxy proxy = new UserBeanProxy(targetObject); 
    // Generate proxy objects      
    UserBean object = (UserBean)Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),  
        targetObject.getClass().getInterfaces(), proxy); 
    object.addUser(); 
     
    System.out.println("NO Proved............."); 
    targetObject = new UserBeanImpl();    
    proxy = new UserBeanProxy(targetObject); 
    // Generate proxy objects      
    object = (UserBean)Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),  
        targetObject.getClass().getInterfaces(), proxy); 
    object.addUser(); 
         
  } 
} 

 
Output:    


Proved............. 
this is addUser() method! 
NO Proved............. 

 
From the above example, you can successfully intercept the called method addUser() and handle it accordingly,    
 

2, create the proxy class through cglib

The advantage is that our target object is not required to implement the interface  
Original class  


public class ClientBean 
{ 
  private String name = null; 
 
  public ClientBean() 
  { 
 
  } 
 
  public ClientBean(String name) 
  { 
    this.name = name; 
  } 
 
  public void addClient() 
  { 
    System.out.println("this is addClient() method!"); 
  } 
 
  public void deleteClient() 
  { 
    System.out.println("this is deleteClient() method!"); 
  } 
 
  public void getClient() 
  { 
    System.out.println("this is getClient() method!"); 
  } 
 
  public void updateClient() 
  { 
    System.out.println("this is updateClient() method!"); 
  } 
 
  public String getClientName() 
  { 
    return name; 
  } 
 
  public void setClientName(String name) 
  { 
    this.name = name; 
  } 
} 

The proxy class  


public class CalculatorImpl implements Calculator {
  @Override
  public int calculate(int a, int b) {
    return a/b;
  }
}
0

Test class    


public class CalculatorImpl implements Calculator {
  @Override
  public int calculate(int a, int b) {
    return a/b;
  }
}
1

 
Output:  


public class CalculatorImpl implements Calculator {
  @Override
  public int calculate(int a, int b) {
    return a/b;
  }
}
2


Related articles: