Preface (can be skipped)
Before we get started, let’s talk about something else. The last chapter of the Spring AOP Implementation Series was originally planned to cover Spring AOP source code. I was confident about it until I read the source code and realized it wasn’t that simple.
First of all, Spring AOP has a bit too much source code and is not concise enough, which makes writing a lot of trouble. Second, it doesn’t seem to make much sense to explain its implementation entirely based on Spring AOP source code.
So I decided to take a different approach, starting with the features and capabilities of Spring AOP, and then implementing a rough Spring AOP framework with Spring AOP implementation ideas.
Special note: in the process of implementation, due to the length, cut a lot of optimization, especially about factories, lazy loading, caching, concurrency, etc.
Function of split
From the previous chapter, we learned about the features of Spring AOP and what it can do. Let’s extract some of the highlights:
- Note and XML configuration are supported
- Can be integrated with Spring IoC
- Supports JDK Dynamic Proxy and CGLIB Proxy
- Integrate With AspectJ’s annotations, including aspects (@aspect), pointcuts (@pointcut), Advice (@advice), and so on
- Implement calls to Advice in a dynamic proxy
We do a functional analysis for these features, which roughly have the following functions:
-
AOP, which focuses on the implementation of AOP functionality based on methods, is divided into three aspects:
- Integrating AspectJ, including integrating expressions, using Annotations defined by AspectJ, and using tools provided by AspectJ;
- The abstraction layer, which includes Spring’s encapsulation and abstraction of AOP concepts, focuses on advisors, which are unique to Spring.
- Implementation layer, custom MethodInterceptor implements calls to AOP target methods and Advice methods
-
Object proxy: Supports JDK and CGLIB proxy methods to generate corresponding proxy objects based on target objects
-
Configuration parsing supports XML and Annotation configuration parsing, and implements corresponding Advisor parsing according to configuration
-
IoC integration, integration of BeanFactory, to achieve access to beans in IoC container; Implement BeanPostProcesser to incorporate configuration resolution, proxy object creation, and so on into the bean initialization process
Note: Let’s take a look at the official website to explain the Advisor: *
An advisor is like a small self-contained aspect that has a single piece of advice.
* Advisors are composite classes that have a single Advice and can use Pointcut, and can be considered a special Aspect.
After listing the Spring AOP capabilities, let’s discuss the process of implementing the capabilities
Resolution of the process
I simply divide the process into two parts, the proxy creation phase triggered by Spring IoC initialization and the proxy invocation phase triggered by program invocation. See the following figure for details of the process:
Agent Creation phase
-
The Spring IoC container triggers Bean initialization through the BeanPostProcesser interface
-
Call BeanPostProcesser postProcessAfterInitialization interface implementation method
-
Enter the process of building the Advisor and find all matching advisors through reflection
-
Filter out matching advisors
-
Enter the process of creating the proxy, pass the collection of Advisors from the previous process to the proxy object, and determine which proxy method to use based on the rules
Proxy invocation phase
- External method calls. Using the log print aspect as an example, when a method in the log aspect is called, the invocation of the proxy is triggered
- By invoking a callback method, you delegate the target method to the callback method. When generating proxy objects, there are corresponding callbacks, such as CallBack in CGLIB and InvocationHandler in JDK
- In the callback method, build the method interceptor chain (more on interceptor chains later)
- The call is further delegated to the interceptor chain, which does the execution
implementation
After listing the Spring AOP capabilities, let’s move on to the implementation of some of the capabilities
AOP abstract concept definition
First, we abstract the concepts in AspectJ. We simply define the Aspect, JoinPoint, Advice, Pointcut, and other classes.
public class Aspect {}public class JoinPoint {}public interface Pointcut {
public String getExpression(a);
}
public class Advice {
private Method adviceMethod;
private Aspect aspect;
}
Copy the code
SpringAOP introduces the concept of Advisor, and we also define an Advisor class
public class Advisor {
private Advice advice;
private Pointcut pointcut;
private Aspect aspect;
}
Copy the code
To incorporate AspectJ expressions, we took the Pointcut a step further
Add string expression conversion to AspectJ expression (PointcutExpression)
import org.aspectj.weaver.tools.PointcutExpression;
//....
/** * convert to AspectJ pointcut expression *@return* /
public PointcutExpression buildPointcutExpression(a);
Copy the code
AspectJ parser class (PointcutParser) is introduced to implement buildPointcutExpression method
import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutParser;
public class AspectJPointcut implements Pointcut {
public String expression;
public AspectJPointcut(String expression) {
this.expression = expression;
}
@Override
public String getExpression(a) {
return this.expression;
}
@Override
public PointcutExpression buildPointcutExpression(a) {
PointcutParser parser = PointcutParser
.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();
return parser.parsePointcutExpression(this.expression); }}Copy the code
Above, several basic classes are defined. Some people say, why don’t you see BeforeAdvice definitions? I’ll keep that in mind until I introduce method interceptors later.
Agent Creation phase
The IoC integration
The integration itself is relatively simple, implement interface BeanPostProcessor and BeanFactoryAware, directly on the code
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
public abstract class AbstractAOPProxyCreator implements BeanPostProcessor.BeanFactoryAware {
// Subclasses can be implemented
protected void initBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
// Get the matching Advisor
protected abstract List<Advisor> getMatchedAdvisors(a);
// Create a proxy object
protected abstract Object createProxy(List<Advisor> advisors, Object bean);
@Override
public void setBeanFactory(BeanFactory arg0) throws BeansException {}@Override
public Object postProcessBeforeInitialization(Object bean, String beanName){
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName){
returnbean; }}Copy the code
The setBeanFactory method that implements the BeanFactoryAware interface, As well as the BeanPostProcessor interface postProcessAfterInitialization method and postProcessBeforeInitialization method.
Let’s introduce the template method design pattern to formulate the processing flow:
public Object postProcessAfterInitialization(Object bean, String beanName){
// Build all advisors
List<Advisor> advisors = buildAdvisors();
// Get the matching Advisor
advisors = this.getMatchedAdvisors();
// Generate proxy objects based on the obtained Advisor
Object object = createProxy(advisors,bean);
// Return the proxy object
return object == null ? bean : object;
}
Copy the code
Parsing configuration
Parsing the configuration mainly involves using reflection, finding the classes marked by @aspect, then finding @advice, @pointcut, etc., and finally combining these into Advisor instances. The implementation is not complicated and will not be described again
public class AnnotationParser implements ConfigParser {
// Avoid repeated builds and increase caching
private final Map<String, List<Advisor>> cache = new ConcurrentHashMap<>();
@Override
public List<Advisor> parse(a) {
if(cache ! =null) {
return getAdvisorsFromCache();
}
// Get all classes annotated by @aspect
List<Class> allClasses = getAllAspectClasses();
for (Class class1 : allClasses) {
cache.putIfAbsent(class1.getName(), getAdvisorsByAspect(class1));
}
return getAdvisorsFromCache();
}
/** * Generates the Advisor class * from the Aspect class@param class1
* @return* /
private List<Advisor> getAdvisorsByAspect(Class class1) {
List<Advisor> advisors = new ArrayList<>();
for (Method method : getAdvisorMethods(class1)) {
Advisor advisor = getAdvisor(method, class1.newInstance());
advisors.add(advisor);
}
returnadvisors; }}Copy the code
Filter compliant advisors
We filter the proxy target Bean from all advisors using Bean methods and Advisor pointcuts using AspectJ expressions and alignment tools
import org.aspectj.weaver.tools.ShadowMatch;
/** * get the matching * from all advisors@param advisors
* @return* /
public static List<Advisor> getMatchedAdvisors(Class cls, List<Advisor> advisors) {
List<Advisor> aList = new ArrayList<>();
for (Method method : cls.getDeclaredMethods()) {
for (Advisor advisor : advisors) {
ShadowMatch match = advisor.getPointcut()
.buildPointcutExpression()
.matchesMethodExecution(method);
if(match.alwaysMatches()) { aList.add(advisor); }}}return aList;
}
Copy the code
Creating a proxy object
We define a factory to which the proxy object is handed over to be created
public class AOPProxyFactory {
public Object getProxyObject(List<Advisor> advisors, Object bean) {
if(isInterface()) {
return new CglibProxyImpl(advisors,bean).getProxyObject();
} else {
return newJdkDynamicProxyImpl(advisors,bean).getProxyObject(); }}private boolean isInterface(a) {
return false; }}Copy the code
There are also two Proxy methods implemented, JDK Dynamic Proxy and CGLIB, which are explained one by one below
Note: You can ignore the method interceptor chain for now
JDK implementation, need to implement the InvocationHandler interface, and in the interface method invoke method interceptor chain call
public class JdkDynamicProxyImpl extends AOPProxy implements InvocationHandler {
public JdkDynamicProxyImpl(List<Advisor> advisors, Object bean) {
super(advisors, bean);
}
@Override
protected Object getProxyObject(a) {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), ReflectHelper.getInterfaces(this.getTarget().getClass()), this);
}
/** * implements the interface method of InvocationHandler, delegating the call to the interceptor chain */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MyMethodInterceptor[] iterceptors =
AdvisorHelper.getMethodInterceptors(this.getAdvisors(), method);
Object obj = new MethodInterceptorChain(iterceptors)
.intercept(method,args,proxy);
returnobj; }}Copy the code
CGLIB is implemented through callbacks, which require CGLIB’s MethodInterceptor
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxyImpl extends AOPProxy {
public CglibProxyImpl(List<Advisor> advisors, Object bean) {
super(advisors, bean);
}
@Override
protected Object getProxyObject(a) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.getTarget().getClass());
enhancer.setCallback(new AOPInterceptor());
return enhancer.create();
}
/** * Implements cglib interceptors that delegate interceptor calls to the interceptor chain */ in the Intercept
private class AOPInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
MyMethodInterceptor[] iterceptors = AdvisorHelper.getMethodInterceptors(CglibProxyImpl.this.getAdvisors(), method);
Object o = new MethodInterceptorChain(iterceptors)
.intercept(method, args, obj);
returno; }}}Copy the code
Now that we’ve basically implemented the process of creating proxy objects, let’s consider a question
How do we implement calling our defined Advice when we invoke the proxy method?
Consider: implementation of Advice method calls
Simple implementation example
Let’s start with a simple implementation, using the JDK proxy as an example:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return obj;
}
Copy the code
We know that any method that executes a proxy object goes to Invoke (refer back to chapter 3 on the implementation of dynamic proxies for those of you who are not familiar with dynamic proxies), so once inside Invoke we need to make the following decisions:
- Find all advisors associated with the proxy (this is not difficult, the proxy class has advisors)
- The collection of advisors is iterated to determine whether they match one by one. The rules are Method and Pointcut (you can also use the AspectJ utility class here)
- Find the matching Advisor, further find the Advice, and then execute the Advice
Let’s use BeforeAdvice as an example to show how to implement Advice calls
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
BeforeAdvice beforeAdvice = getBeforeAdvice(method);
beforeAdvice.before(proxy, method, args);// Call Advice
return method.invoke(this.bean, args);
}
/** * Take returning BeforeAdvice as an example *@return* /
private BeforeAdvice getBeforeAdvice(Method method) {
for (Advisor advisor : this.getAdvisors()) {
if(AdvisorHelper.isMatch(advisor, method)
&& advisor.getAdvice() instanceof BeforeAdvice) {
return(BeforeAdvice) advisor.getAdvice(); }}return null;
}
Copy the code
So, what if we get matching Advice and AfterAdvice? We add code to the Invoke method
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
BeforeAdvice beforeAdvice = getBeforeAdvice(method);
beforeAdvice.before(proxy, method, args);// Call Advice
Object o = method.invoke(this.bean, args);
AfterAdvice afterAdvice = getAfterAdvice(method);
afterAdvice.after(proxy, method, args);// Call Advice
return o;
}
Copy the code
So what if we get two or more of the same type of Advice? And there is an execution order requirement between Advice of the same type. This simple implementation does not suffice, and we need to introduce a chain of method interceptors
Chain of responsibility design patterns
As a simple extension, many interceptors and filters are implemented based on the responsibility chain pattern. Before defining the method interceptor chain, let’s take a look at how Tomcat implements filters.
Note: to be exact, Tomcat is implemented based on the JavaEE standard
Java servlet interface
public interface FilterChain {
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException;
}
public interface Filter {
public void init(FilterConfig filterConfig) throws ServletException;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
public void destroy(a);
}
Copy the code
Tomcat filter chain implementation
public final class ApplicationFilterChain implements FilterChain {
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
void addFilter(ApplicationFilterConfig filterConfig) {
/ /..
}
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
//C-1
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
filter.doFilter(request, response, this);
return;
}
// We fell off the end of the chain -- call the servlet instanceservlet.service(request, response); }}Copy the code
Session initializes the filter
public class SessionInitializerFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
((HttpServletRequest)request).getSession();
//C-2
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// NO-OP
}
@Override
public void destroy(a) {
// NO-OP}}Copy the code
The above implementation has several key points:
- ApplicationFilterChain controls the execution of the chain of responsibilities, which can also be used to sort elements or match rules, as shown in section C-1
- ApplicationFilterChain implements chain calls through recursive calls
- Each Filter can be registered/added to the chain of execution and passed to ApplicationFilterChain (chain-.dofilter) after it has executed itself.
Implement the method interceptor chain
We introduce the chain of responsibilities pattern, abstracting Advice into a MethodInterceptor. This has the following benefits for feature implementation:
- Advice can be added dynamically
- You can sort for Advice, rule matching, and so on
- Individual Advice can implement its own business logic individually (such as BeforeAdvice), and it is easy to extend new Advice later
Define an interceptor. To distinguish it from CGLIB’s interceptor, we’ll call it MyMethodInterceptor
public interface MyMethodInterceptor {
public Object intercept(Method method, Object[] arguments, Object target, MethodInterceptorChain chain);
}
Copy the code
Define BeforeAdvice and AfterAdvice
public class BeforeAdvice extends Advice implements MyMethodInterceptor {
public BeforeAdvice(Method adviceMethod, Aspect aspect) {
super(adviceMethod, aspect);
}
public void before(final Object target, final Method method, final Object[] args) {
this.invokeAspectMethod(target, method, args);
;
}
@Override
public Object intercept(Method method, Object[] arguments, Object target, MethodInterceptorChain chain) {
this.before(target, method, arguments);
returnchain.intercept(method, arguments, target); }}public class AfterAdvice extends Advice implements MyMethodInterceptor {
public AfterAdvice(Method adviceMethod, Aspect aspect) {
super(adviceMethod, aspect);
}
public void after(final Object target, final Method method, final Object[] args) {
this.invokeAspectMethod(target, method, args);
}
@Override
public Object intercept(Method method, Object[] arguments, Object target, MethodInterceptorChain chain) {
Object obj = chain.intercept(method, arguments, target);
this.after(target, method, arguments);
returnobj; }}Copy the code
Implement the method interceptor chain
public class MethodInterceptorChain {
private MyMethodInterceptor[] methodInterceptors;
public MethodInterceptorChain(MyMethodInterceptor[] methodInterceptors) {
this.methodInterceptors = methodInterceptors;
}
private int index = 0;
public Object intercept(Method method, Object[] arguments, Object target) {
if (index == methodInterceptors.length) {
// call method
return method.invoke(target, arguments);
} else {
return methodInterceptors[index++]
.intercept(method, arguments, target, this);
}
return null; }}Copy the code
So, going back to our original thinking, we can replace our simple implementation with a MethodInterceptorChain as follows:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MyMethodInterceptor[] iterceptors =
AdvisorHelper.getMethodInterceptors(this.getAdvisors(), method);
Object obj = new MethodInterceptorChain(iterceptors)
.intercept(method,args,proxy);
return obj;
}
Copy the code
In this way, we move the invocation of the proxy method to the MethodInterceptorChain
Finally, there is a small hidden problem that the Advisor in the proxy object is all related to this class. We still need to find the interceptor that matches the method based on method and Pointcut. This is the same as the previous implementation of filtering Advisor, which is based on AspectJ
The last
After the method interceptor chain is covered, the flow of proxy invocation is clear and will not be described again.
Our implementation is only Annotation configuration based on AspectJ. Spring AOP also supports Schema configuration. Time and space reasons, I will not do in-depth discussion.
In addition to the chain of responsibility pattern highlighted in this article, Spring AOP employs a number of factory patterns, template method patterns, adapter patterns, and more. Especially when a large number of factories (such as Aspect factory, Advisor factory, etc.) are used in conjunction with Spring IoC, it can support powerful management of classes and objects (Aspect, Advisor, etc.), including loading strategies such as singleton, multi-instance, lazy loading, etc. The author thinks, these are worth everybody thorough study and research.
The appendix
Code address: github.com/wanghe9011/…