introduce
As we all know, AOP (section-oriented programming) is one of the features of Spring frameworks. AOP provides extremely high scalability by setting cross cutting concerns. So how does AOP work in Spring? When you can only use core java but need AOP technology, the answer to this question becomes extremely critical. Not only that, in interviews for advanced technical positions, such questions often appear as test questions. Look, my friend recently attended an interview and was asked such a difficult question - how to implement AOP without using Spring and related libraries, and only core Java. Therefore, I will provide an outline in this article to help you understand how to implement an AOP using core Java (of course, this AOP has certain functional limitations). Note that this article is not a comparative study of Spring AOP and Java AOP, but a tutorial on implementing AOP with the help of inherent design patterns in core Java.
I believe readers already know what AOP is and how to use it in the Spring framework, so this article only focuses on how to implement AOP without using Spring. First of all, we must know that Spring uses two technologies: JDK proxy and CGlib to implement AOP. JDK dynamic proxy provides a flexible way to hook a method and perform specified operations, but there must be a restriction when performing operations: a related interface and the implementation class of the interface must be provided first. Practice to create true knowledge, let us understand this sentence through a case! There is now a calculator program for doing some math operations. Let's consider the division function. The question at this time is: If the core framework already has a code to implement division, can we hijack it and perform additional verification when the code is executed? The answer is yes, and I will prove this with the code snippet provided below. First, let’s look at the code of the basic interface:
public interface Calculator { public int calculate( int a , int b);}The code of this 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 cannot repair the above code or make any changes to the core library, how can we perfectly implement the verification function? Why not try the JDK dynamic proxy function.
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 see how the verification function implemented by JDK dynamic proxy works through the test class.
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); }From the results, we can see that by simply implementing the powerful InvocationHandler interface, we can get a hooking implementation. According to the JDK documentation, the InvocationHandler interface uses a proxy instance to handle a method call.
Now we know that the invoke() method of InvocationHandler can help us solve the problem. So let’s solve a new problem - how can we perform operations before and after the method execution? To put it more specifically, can we hook a method by adding multiple aops (before, after, around) (translator's note: the original text is add multiple aops, but I think Handler acts as an Aspect)? The answer is also yes. Follow the steps below to create a streamlined code template to meet this requirement:
Two ways to implement AOP:
1. Dynamic proxy implementation provided by JDK
interface
public interface UserBean { void getUser(); void addUser(); void updateUser(); void deleteUser(); } 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!"); } } Agent 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; //Permission judgment 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 object 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 object 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, the called method addUser() can be successfully intercepted and processed accordingly.
2. Create proxy class through cglib
The advantage is that our target object does not require the interface primitive class to be implemented.
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 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; } } Agent class
import java.lang.reflect.Method; import com.cignacmc.finance.bean.ClientBean; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CGLibProxy implements MethodInterceptor { private Object targetObject; public Object createProxyObject(Object targetObject) { this.targetObject = targetObject; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.targetObject.getClass()); enhancer.setCallback(this); return enhancer.create(); } public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { ClientBean clientBean = (ClientBean)targetObject; String userName = clientBean.getClientName(); Object result = null; if(userName != null && !"".equals(userName)) { result = method.invoke(targetObject, args); } return result; } } Test class
import java.lang.reflect.Proxy; import com.cignacmc.finance.bean.ClientBean; import com.cignacmc.finance.bean.UserBean; import com.cignacmc.finance.bean.UserBeanImpl; import com.cignacmc.finance.proxy.CGLibProxy; import com.cignacmc.finance.proxy.UserBeanProxy; public class ProxyExe { public static void main(String[] args) { System.out.println("........CGLIB Proxy............"); System.out.println("Proved..............."); CGLibProxy cproxy = new CGLibProxy(); ClientBean clientBean = (ClientBean)cproxy.createProxyObject(new ClientBean("Bob Liang")); clientBean.addClient(); System.out.println("NO Proved..............."); cproxy = new CGLibProxy(); clientBean = (ClientBean)cproxy.createProxyObject(new ClientBean()); clientBean.addClient(); } }
Output:
.....CGLIB Proxy..................Proved............ this is addClient() method! NO Proved............