This article is excerpted from Spring 5 core principles
Previously, we have completed the functions of the three core modules of Spring IoC, DI and MVC, and ensured that the functions are available. The next step is to complete another core module of Spring - AOP, which is also the most difficult part.
1 basic configuration
First, in application Add the following custom configuration in properties as the basic configuration of Spring AOP:
#For multi aspect configuration, you can prefix the key #For example, aspect logAspect. #Slice expression# pointCut=public .* com.tom.spring.demo.service..*Service..*(.*) #Section class# aspectClass=com.tom.spring.demo.aspect.LogAspect #Slice pre notification# aspectBefore=before #Slice post notification# aspectAfter=after #Section exception notification# aspectAfterThrow=afterThrowing #Section exception type# aspectAfterThrowingName=java.lang.Exception
To enhance our understanding, let's compare the native configuration of Spring AOP:
<bean id="xmlAspect" class="com.gupaoedu.aop.aspect.XmlAspect"></bean> <!-- AOP to configure --> <aop:config> <!-- Declare a slice and inject the slice Bean,amount to@Aspect --> <aop:aspect ref="xmlAspect"> <!-- Configuring a pointcut is equivalent to@Pointcut --> <aop:pointcut expression="execution(* com.gupaoedu.aop.service..*(..))" id="simplePointcut"/> <!-- Configure notifications, equivalent to@Before,@After,@AfterReturn,@Around,@AfterThrowing --> <aop:before pointcut-ref="simplePointcut" method="before"/> <aop:after pointcut-ref="simplePointcut" method="after"/> <aop:after-returning pointcut-ref="simplePointcut" method="afterReturn"/> <aop:after-throwing pointcut-ref="simplePointcut" method="afterThrow" throwing="ex"/> </aop:aspect> </aop:config>
For convenience, we use the properties file instead of XML to simplify the operation.
2 AOP core principle v1 0 version
The basic implementation principle of AOP is to use the dynamic agent mechanism to create a new agent class to complete code weaving, so as to achieve the purpose of code function enhancement. If you don't know much about the principle of dynamic agent, you can go back to the special article on dynamic agent mode in the "design patterns should be learned this way" series I updated some time ago. So how does Spring AOP work with dynamic proxies? In fact, the main function of Spring is to complete decoupling, separate the code logic we need to enhance into special classes, then associate these separated logic through the declaration configuration file, and finally merge them to run together. In order to save this relationship in the Spring container, we can simply understand that Spring uses a Map to save this relationship. The key of Map is the target method we want to call, and the value of Map is the method we want to weave in. However, the methods to be woven are in sequence, so we need to mark the position of the weaving methods. The logic woven in front of the target method is called pre notification, the logic woven behind the target method is called post notification, and the logic that needs to be woven in case of exception in the target method is called exception notification. The specific design of Map is as follows:
private Map<Method,Map<String, Method>> methodAdvices = new HashMap<Method, Map<String, Method>>();
I'll write a simple ApplicationContex in full below. You can refer to it:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; public class GPApplicationContext { private Properties contextConfig = new Properties(); private Map<String,Object> ioc = new HashMap<String,Object>(); //It is used to save the correspondence between the corresponding Method and Advice in the configuration file private Map<Method,Map<String, Method>> methodAdvices = new HashMap<Method, Map<String, Method>>(); public GPApplicationContext(){ //For demonstration purposes, manually initialize a Bean ioc.put("memberService", new MemberService()); doLoadConfig("application.properties"); doInitAopConfig(); } public Object getBean(String name){ return createProxy(ioc.get(name)); } private Object createProxy(Object instance){ return new GPJdkDynamicAopProxy(instance).getProxy(); } //Load profile private void doLoadConfig(String contextConfigLocation) { //Directly find the path where the Spring main configuration file is located from the classpath //And read it out and put it in the Properties object //Relative to scanpackage = com gupaoedu. The demo is saved from the file to memory InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation); try { contextConfig.load(is); } catch (IOException e) { e.printStackTrace(); }finally { if(null != is){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } } private void doInitAopConfig() { try { Class apectClass = Class.forName(contextConfig.getProperty("aspectClass")); Map<String,Method> aspectMethods = new HashMap<String,Method>(); for (Method method : apectClass.getMethods()) { aspectMethods.put(method.getName(),method); } //The PonintCut expression resolves to a regular expression String pointCut = contextConfig.getProperty("pointCut") .replaceAll("\\.","\\\\.") .replaceAll("\\\\.\\*",".*") .replaceAll("\\(","\\\\(") .replaceAll("\\)","\\\\)"); Pattern pointCutPattern = Pattern.compile(pointCut); for (Map.Entry<String,Object> entry : ioc.entrySet()) { Class<?> clazz = entry.getValue().getClass(); //Loop to find all methods for (Method method : clazz.getMethods()) { //Save method name String methodString = method.toString(); if(methodString.contains("throws")){ methodString = methodString.substring(0,methodString.lastIndexOf("throws")).trim(); } Matcher matcher = pointCutPattern.matcher(methodString); if(matcher.matches()){ Map<String,Method> advices = new HashMap<String,Method>(); if(!(null == contextConfig.getProperty("aspectBefore") || "".equals( contextConfig.getProperty("aspectBefore")))){ advices.put("before",aspectMethods.get(contextConfig.getProperty("aspectBefore"))); } if(!(null == contextConfig.getProperty("aspectAfter") || "".equals( contextConfig.getProperty("aspectAfter")))){ advices.put("after",aspectMethods.get(contextConfig.getProperty("aspectAfter"))); } if(!(null == contextConfig.getProperty("aspectAfterThrow") || "".equals( contextConfig.getProperty("aspectAfterThrow")))){ advices.put("afterThrow",aspectMethods.get(contextConfig.getProperty("aspectAfterThrow"))); } methodAdvices.put(method,advices); } } } }catch (Exception e){ e.printStackTrace(); } } class GPJdkDynamicAopProxy implements GPInvocationHandler { private Object instance; public GPJdkDynamicAopProxy(Object instance) { this.instance = instance; } public Object getProxy() { return Proxy.newProxyInstance(instance.getClass().getClassLoader(),instance.getClass().getInterfaces(),this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object aspectObject = Class.forName(contextConfig.getProperty("aspectClass")).newInstance(); Map<String,Method> advices = methodAdvices.get(instance.getClass().getMethod(method.getName(),method.getParameterTypes())); Object returnValue = null; advices.get("before").invoke(aspectObject); try { returnValue = method.invoke(instance, args); }catch (Exception e){ advices.get("afterThrow").invoke(aspectObject); e.printStackTrace(); throw e; } advices.get("after").invoke(aspectObject); return returnValue; } } }
Test code:
public class MemberServiceTest { public static void main(String[] args) { GPApplicationContext applicationContext = new GPApplicationContext(); IMemberService memberService = (IMemberService)applicationContext.getBean("memberService"); try { memberService.get("1"); memberService.save(new Member()); } catch (Exception e) { e.printStackTrace(); } } }
We can fully demonstrate the core principles of Spring AOP through a few hundred lines of code. Is it very simple? Of course, the little friends still have to do it by themselves, ha, experience it personally, so as to be impressed. Next, let's continue to improve and upgrade Spring AOP 1.0 to 2.0. I wrote version 2.0 by completely simulating the original design of Spring. I hope it can bring you a different handwriting experience and a deeper understanding of the principle of Spring AOP.
3. Complete AOP top-level design
3.1 GPJoinPoint
Define a pointcut abstraction, which is the basic unit of AOP. We can understand that this is additional information of a business method. It is conceivable that the pointcut should include the business method itself, the argument list and the instance object to which the method belongs. You can also add custom attributes in GPJoinPoint. See the following code:
package com.tom.spring.formework.aop.aspect; import java.lang.reflect.Method; /** * Callback the connection point, through which you can get all the information of the proxy business method */ public interface GPJoinPoint { Method getMethod(); //Business method itself Object[] getArguments(); //List of arguments for the method Object getThis(); //The instance object to which the method belongs //Add custom properties in JoinPoint void setUserAttribute(String key, Object value); //Get a property value from the added custom property Object getUserAttribute(String key); }
3.2 GPMethodInterceptor
Method interceptor is the basic unit of AOP code enhancement. Its subclasses mainly include GPMethodBeforeAdvice, GPAfterReturningAdvice and GPAfterThrowingAdvice.
package com.tom.spring.formework.aop.intercept; /** * Method interceptor top-level interface */ public interface GPMethodInterceptor{ Object invoke(GPMethodInvocation mi) throws Throwable; }
3.3 GPAopConfig
Define the encapsulation object of AOP configuration information to facilitate mutual transmission in subsequent code.
package com.tom.spring.formework.aop; import lombok.Data; /** * AOP Configuration encapsulation */ @Data public class GPAopConfig { //The following configuration corresponds to the properties in the properties file one by one private String pointCut; //Slice expression private String aspectBefore; //Pre notification method name private String aspectAfter; //Post notification method name private String aspectClass; //Facet class to weave in private String aspectAfterThrow; //Exception notification method name private String aspectAfterThrowingName; //Exception type to be notified }
3.4 GPAdvisedSupport
Gpadvised support mainly completes the analysis of AOP configuration. The pointCutMatch() method is used to judge whether the target class conforms to the facet rules, so as to determine whether the proxy class needs to be generated and enhance the target method. The getInterceptorsAndDynamic- InterceptionAdvice() method mainly encapsulates the method to be recalled into an interceptor chain according to the AOP configuration, and returns it for external acquisition.
package com.tom.spring.formework.aop.support; import com.tom.spring.formework.aop.GPAopConfig; import com.tom.spring.formework.aop.aspect.GPAfterReturningAdvice; import com.tom.spring.formework.aop.aspect.GPAfterThrowingAdvice; import com.tom.spring.formework.aop.aspect.GPMethodBeforeAdvice; import java.lang.reflect.Method; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * It is mainly used to parse and encapsulate AOP configuration */ public class GPAdvisedSupport { private Class targetClass; private Object target; private Pattern pointCutClassPattern; private transient Map<Method, List<Object>> methodCache; private GPAopConfig config; public GPAdvisedSupport(GPAopConfig config){ this.config = config; } public Class getTargetClass() { return targetClass; } public void setTargetClass(Class targetClass) { this.targetClass = targetClass; parse(); } public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; } public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) throws Exception { List<Object> cached = methodCache.get(method); //If the cache misses, proceed to the next step if (cached == null) { Method m = targetClass.getMethod(method.getName(),method.getParameterTypes()); cached = methodCache.get(m); //Store in cache this.methodCache.put(m, cached); } return cached; } public boolean pointCutMatch(){ return pointCutClassPattern.matcher(this.targetClass.toString()).matches(); } private void parse(){ //pointCut expression String pointCut = config.getPointCut() .replaceAll("\\.","\\\\.") .replaceAll("\\\\.\\*",".*") .replaceAll("\\(","\\\\(") .replaceAll("\\)","\\\\)"); String pointCutForClass = pointCut.substring(0,pointCut.lastIndexOf("\\(") - 4); pointCutClassPattern = Pattern.compile("class " + pointCutForClass.substring (pointCutForClass.lastIndexOf(" ")+1)); methodCache = new HashMap<Method, List<Object>>(); Pattern pattern = Pattern.compile(pointCut); try { Class aspectClass = Class.forName(config.getAspectClass()); Map<String,Method> aspectMethods = new HashMap<String,Method>(); for (Method m : aspectClass.getMethods()){ aspectMethods.put(m.getName(),m); } //The methods obtained here are native methods for (Method m : targetClass.getMethods()){ String methodString = m.toString(); if(methodString.contains("throws")){ methodString = methodString.substring(0,methodString.lastIndexOf("throws")).trim(); } Matcher matcher = pattern.matcher(methodString); if(matcher.matches()){ //Classes that meet the section rules are added to the AOP configuration List<Object> advices = new LinkedList<Object>(); //Before advice if(!(null == config.getAspectBefore() || "".equals(config.getAspectBefore().trim()))) { advices.add(new GPMethodBeforeAdvice(aspectMethods.get (config.getAspectBefore()), aspectClass.newInstance())); } //Post notification if(!(null == config.getAspectAfter() || "".equals(config.getAspectAfter(). trim()))) { advices.add(new GPAfterReturningAdvice(aspectMethods.get (config.getAspectAfter()), aspectClass.newInstance())); } //Exception notification if(!(null == config.getAspectAfterThrow() || "".equals(config.getAspectAfterThrow().trim()))) { GPAfterThrowingAdvice afterThrowingAdvice = new GPAfterThrowingAdvice (aspectMethods.get(config.getAspectAfterThrow()), aspectClass.newInstance()); afterThrowingAdvice.setThrowingName(config.getAspectAfterThrowingName()); advices.add(afterThrowingAdvice); } methodCache.put(m,advices); } } } catch (Exception e) { e.printStackTrace(); } } }
3.5 GPAopProxy
GPAopProxy is the top-level interface of the proxy factory. There are two main subclasses: GPCglibAopProxy and GPJdkDynamicAopProxy, which implement CGlib proxy and JDK Proxy respectively.
package com.tom.spring.formework.aop; /** * The top-level interface of the agent factory provides the top-level entry to obtain the agent object */ //JDK dynamic proxy is used by default public interface GPAopProxy { //Get a proxy object Object getProxy(); //Get a proxy object through a custom class loader Object getProxy(ClassLoader classLoader); }
3.6 GPCglibAopProxy
CglibAopProxy is not implemented in this article. Interested "small partners" can try it by themselves.
package com.tom.spring.formework.aop; import com.tom.spring.formework.aop.support.GPAdvisedSupport; /** * Use CGlib API to generate proxy classes. No examples are given here * Interested "small partners" can be realized by themselves */ public class GPCglibAopProxy implements GPAopProxy { private GPAdvisedSupport config; public GPCglibAopProxy(GPAdvisedSupport config){ this.config = config; } @Override public Object getProxy() { return null; } @Override public Object getProxy(ClassLoader classLoader) { return null; } }
3.7 GPJdkDynamicAopProxy
Let's look at the implementation of GPJdkDynamicAopProxy. The main function is in the invoke() method. In terms of the amount of code, it's not much. It's mainly by calling the getinterceptorsanddynamicinception advice() method of GPAdvisedSupport to obtain the interceptor chain. In the target class, each enhanced target method corresponds to an interceptor chain.
package com.tom.spring.formework.aop; import com.tom.spring.formework.aop.intercept.GPMethodInvocation; import com.tom.spring.formework.aop.support.GPAdvisedSupport; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; /** * Generate proxy classes using JDK Proxy API */ public class GPJdkDynamicAopProxy implements GPAopProxy,InvocationHandler { private GPAdvisedSupport config; public GPJdkDynamicAopProxy(GPAdvisedSupport config){ this.config = config; } //Pass in native objects public Object getProxy(){ return getProxy(this.config.getTargetClass().getClassLoader()); } @Override public Object getProxy(ClassLoader classLoader) { return Proxy.newProxyInstance(classLoader,this.config.getTargetClass().getInterfaces(),this); } //The invoke() method is the key entry point for executing the proxy @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //Encapsulate each JoinPoint, that is, the proxy's business Method, into an interceptor and combine it into an interceptor chain List<Object> interceptorsAndDynamicMethodMatchers = config.getInterceptorsAndDynamicInterceptionAdvice(method,this.config.getTargetClass()); //The interceptor chain is executed by the processed() method of MethodInvocation GPMethodInvocation invocation = new GPMethodInvocation(proxy,this.config.getTarget(), method,args,this.config.getTargetClass(),interceptorsAndDynamicMethodMatchers); return invocation.proceed(); } }
As can be seen from the code, the interceptor chain obtained from gpadvised support is passed into the construction method of GPMethodInvocation as a parameter. So what is done to the method chain in GPMethodInvocation?
3.8 GPMethodInvocation
The code of GPMethodInvocation is as follows:
package com.tom.spring.formework.aop.intercept; import com.tom.spring.formework.aop.aspect.GPJoinPoint; import java.lang.reflect.Method; import java.util.List; /** * Execute interceptor chain, which is equivalent to the function of reflective method invocation in Spring */ public class GPMethodInvocation implements GPJoinPoint { private Object proxy; //Proxy object private Method method; //Target method of agent private Object target; //Target object of proxy private Class<?> targetClass; //Target class of agent private Object[] arguments; //List of arguments for the proxy's method private List<Object> interceptorsAndDynamicMethodMatchers; //Callback method chain //Save custom attributes private Map<String, Object> userAttributes; private int currentInterceptorIndex = -1; public GPMethodInvocation(Object proxy, Object target, Method method, Object[] arguments, Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) { this.proxy = proxy; this.target = target; this.targetClass = targetClass; this.method = method; this.arguments = arguments; this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers; } public Object proceed() throws Throwable { //If the Interceptor has finished executing, execute joinPoint if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { return this.method.invoke(this.target,this.arguments); } Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); //If you want to dynamically match joinpoints if (interceptorOrInterceptionAdvice instanceof GPMethodInterceptor) { GPMethodInterceptor mi = (GPMethodInterceptor) interceptorOrInterceptionAdvice; return mi.invoke(this); } else { //Execute the current intercetor return proceed(); } } @Override public Method getMethod() { return this.method; } @Override public Object[] getArguments() { return this.arguments; } @Override public Object getThis() { return this.target; } public void setUserAttribute(String key, Object value) { if (value != null) { if (this.userAttributes == null) { this.userAttributes = new HashMap<String,Object>(); } this.userAttributes.put(key, value); } else { if (this.userAttributes != null) { this.userAttributes.remove(key); } } } public Object getUserAttribute(String key) { return (this.userAttributes != null ? this.userAttributes.get(key) : null); } }
As can be seen from the code, the processed () method is the key to MethodInvocation. In process (), judge first. If the interceptor chain is empty, it means that the target method does not need to be enhanced. Call the target method directly and return. If the interceptor chain is not empty, the methods in the interceptor chain will be executed in sequence until all the methods in the interceptor chain are executed.
4 design AOP basic implementation
4.1 GPAdvice
As the top-level interface design of all callback notifications, GPAdvice is designed as a specification and does not implement any functions in the Mini version in order to be consistent with the native Spring as much as possible.
/** * Callback notification top-level interface */ public interface GPAdvice { }
4.2 GPAbstractAspectJAdvice
The GPAbstractAspectJAdvice class is designed using template pattern to encapsulate the general logic of interceptor callback. It mainly encapsulates reflection dynamic call methods. Its subclasses only need to control the call order.
package com.tom.spring.formework.aop.aspect; import java.lang.reflect.Method; /** * Encapsulates the general logic of interceptor callback. In the Mini version, it mainly encapsulates the reflection dynamic call method */ public abstract class GPAbstractAspectJAdvice implements GPAdvice { private Method aspectMethod; private Object aspectTarget; public GPAbstractAspectJAdvice( Method aspectMethod, Object aspectTarget) { this.aspectMethod = aspectMethod; this.aspectTarget = aspectTarget; } //Reflection dynamic call method protected Object invokeAdviceMethod(GPJoinPoint joinPoint,Object returnValue,Throwable ex) throws Throwable { Class<?> [] paramsTypes = this.aspectMethod.getParameterTypes(); if(null == paramsTypes || paramsTypes.length == 0) { return this.aspectMethod.invoke(aspectTarget); }else { Object[] args = new Object[paramsTypes.length]; for (int i = 0; i < paramsTypes.length; i++) { if(paramsTypes[i] == GPJoinPoint.class){ args[i] = joinPoint; }else if(paramsTypes[i] == Throwable.class){ args[i] = ex; }else if(paramsTypes[i] == Object.class){ args[i] = returnValue; } } return this.aspectMethod.invoke(aspectTarget,args); } } }
4.3 GPMethodBeforeAdvice
GPMethodBeforeAdvice inherits GPAbstractAspectJAdvice, implements GPAdvice and GPMethodInterceptor interfaces, and controls the calling order of pre notification in invoke().
package com.tom.spring.formework.aop.aspect; import com.tom.spring.formework.aop.intercept.GPMethodInterceptor; import com.tom.spring.formework.aop.intercept.GPMethodInvocation; import java.lang.reflect.Method; /** * Implementation of pre notification */ public class GPMethodBeforeAdvice extends GPAbstractAspectJAdvice implements GPAdvice, GPMethodInterceptor { private GPJoinPoint joinPoint; public GPMethodBeforeAdvice(Method aspectMethod, Object target) { super(aspectMethod, target); } public void before(Method method, Object[] args, Object target) throws Throwable { invokeAdviceMethod(this.joinPoint,null,null); } public Object invoke(GPMethodInvocation mi) throws Throwable { this.joinPoint = mi; this.before(mi.getMethod(), mi.getArguments(), mi.getThis()); return mi.proceed(); } }
4.4 GPAfterReturningAdvice
GPAfterReturningAdvice inherits GPAbstractAspectJAdvice, implements GPAdvice and GPMethodInterceptor interfaces, and controls the calling order of post notification in invoke().
package com.tom.spring.formework.aop.aspect; import com.tom.spring.formework.aop.intercept.GPMethodInterceptor; import com.tom.spring.formework.aop.intercept.GPMethodInvocation; import java.lang.reflect.Method; /** * Implementation of post notification */ public class GPAfterReturningAdvice extends GPAbstractAspectJAdvice implements GPAdvice, GPMethodInterceptor { private GPJoinPoint joinPoint; public GPAfterReturningAdvice(Method aspectMethod, Object target) { super(aspectMethod, target); } @Override public Object invoke(GPMethodInvocation mi) throws Throwable { Object retVal = mi.proceed(); this.joinPoint = mi; this.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis()); return retVal; } public void afterReturning(Object returnValue, Method method, Object[] args,Object target) throws Throwable{ invokeAdviceMethod(joinPoint,returnValue,null); } }
4.5 GPAfterThrowingAdvice
GPAfterThrowingAdvice inherits GPAbstractAspectJAdvice, implements GPAdvice and GPMethodInterceptor interfaces, and controls the call order of exception notification in invoke().
package com.tom.spring.formework.aop.aspect; import com.tom.spring.formework.aop.intercept.GPMethodInterceptor; import com.tom.spring.formework.aop.intercept.GPMethodInvocation; import java.lang.reflect.Method; /** * Implementation of exception notification */ public class GPAfterThrowingAdvice extends GPAbstractAspectJAdvice implements GPAdvice, GPMethodInterceptor { private String throwingName; private GPMethodInvocation mi; public GPAfterThrowingAdvice(Method aspectMethod, Object target) { super(aspectMethod, target); } public void setThrowingName(String name) { this.throwingName = name; } @Override public Object invoke(GPMethodInvocation mi) throws Throwable { try { return mi.proceed(); }catch (Throwable ex) { invokeAdviceMethod(mi,null,ex.getCause()); throw ex; } } }
Interested "partners" can refer to the Spring source code and implement the call logic around the notification by themselves.
4.6 accessing getBean() method
In the above code, we have completed the core functions of the Spring AOP module, so how to integrate it into the IoC container next? Find the getBean() method of GPApplicationContext. We know that the method responsible for Bean initialization in getBean() is actually instantiateBean(). During initialization, we can determine whether to return the native Bean or Proxy Bean. The code implementation is as follows:
//Pass a BeanDefinition and return an instance Bean private Object instantiateBean(GPBeanDefinition beanDefinition){ Object instance = null; String className = beanDefinition.getBeanClassName(); try{ //Because you can only determine whether a Class has an instance according to Class if(this.singletonBeanCacheMap.containsKey(className)){ instance = this.singletonBeanCacheMap.get(className); }else{ Class<?> clazz = Class.forName(className); instance = clazz.newInstance(); GPAdvisedSupport config = instantionAopConfig(beanDefinition); config.setTargetClass(clazz); config.setTarget(instance); if(config.pointCutMatch()) { instance = createProxy(config).getProxy(); } this.factoryBeanObjectCache.put(className,instance); this.singletonBeanCacheMap.put(beanDefinition.getFactoryBeanName(),instance); } return instance; }catch (Exception e){ e.printStackTrace(); } return null; } private GPAdvisedSupport instantionAopConfig(GPBeanDefinition beanDefinition) throws Exception{ GPAopConfig config = new GPAopConfig(); config.setPointCut(reader.getConfig().getProperty("pointCut")); config.setAspectClass(reader.getConfig().getProperty("aspectClass")); config.setAspectBefore(reader.getConfig().getProperty("aspectBefore")); config.setAspectAfter(reader.getConfig().getProperty("aspectAfter")); config.setAspectAfterThrow(reader.getConfig().getProperty("aspectAfterThrow")); config.setAspectAfterThrowingName(reader.getConfig().getProperty("aspectAfterThrowingName")); return new GPAdvisedSupport(config); } private GPAopProxy createProxy(GPAdvisedSupport config) { Class targetClass = config.getTargetClass(); if (targetClass.getInterfaces().length > 0) { return new GPJdkDynamicAopProxy(config); } return new GPCglibAopProxy(config); }
As you can see from the above code, calling createProxy() in the instantiateBean() method determines the invocation strategy of the proxy factory, and then invokes the proxy() method of the agent factory to create the proxy object. The final proxy object will be encapsulated in BeanWrapper and saved to IoC container.
5. Weaving business code
Through the previous code writing, all core modules and underlying logic have been realized, "everything is ready, only the east wind." Next, it's "the time to witness miracles". Let's weave business code and do a test. Create a LogAspect class to monitor business methods. It mainly records the call log of the target method, and obtains the target method name, argument list and the time consumed by each call.
5.1 LogAspect
The code of LogAspect is as follows:
package com.tom.spring.demo.aspect; import com.tom.spring.formework.aop.aspect.GPJoinPoint; import lombok.extern.slf4j.Slf4j; import java.util.Arrays; /** * Define an embedded slice logic, that is, the logic to be enhanced for the target proxy object * This class is mainly used to monitor method calls and monitor the time consumed by each execution of the target method */ @Slf4j public class LogAspect { //Before calling a method, execute the before() method public void before(GPJoinPoint joinPoint){ joinPoint.setUserAttribute("startTime_" + joinPoint.getMethod().getName(),System.currentTimeMillis()); //The logic in this method is written by ourselves log.info("Invoker Before Method!!!" + "\nTargetObject:" + joinPoint.getThis() + "\nArgs:" + Arrays.toString(joinPoint.getArguments())); } //After a method is called, the after() method is executed public void after(GPJoinPoint joinPoint){ log.info("Invoker After Method!!!" + "\nTargetObject:" + joinPoint.getThis() + "\nArgs:" + Arrays.toString(joinPoint.getArguments())); long startTime = (Long) joinPoint.getUserAttribute("startTime_" + joinPoint.getMethod().getName()); long endTime = System.currentTimeMillis(); System.out.println("use time :" + (endTime - startTime)); } public void afterThrowing(GPJoinPoint joinPoint, Throwable ex){ log.info("An exception occurred" + "\nTargetObject:" + joinPoint.getThis() + "\nArgs:" + Arrays.toString(joinPoint.getArguments()) + "\nThrows:" + ex.getMessage()); } }
Through the above code, we can find that each callback method adds a parameter GPJoinPoint. Remember what GPJoinPoint is? In fact, GPMethodInvocation is the implementation class of GPJoinPoint. GPMethodInvocation is instantiated in the invoke() method of GPJdkDynamicAopPorxy, that is, the business method of each proxy object will correspond to a GPMethodInvocation instance. That is, the lifecycle of MethodInvocation corresponds to the lifecycle of the business method in the proxy object. As we have seen earlier, calling the setUserAttribute() method of GPJoinPoint can customize the attribute in GPJoinPoint, and calling the getUserAttribute() method can obtain the value of the custom attribute. In the before() method of LogAspect, set startTime in GPJoinPoint and assign it as system time, that is, record the context from the start time of method call to MethodInvocation. Obtain startTime in the after() method of LogAspect, and save the system time obtained again to endTime. In the AOP interceptor chain callback, the before() method must be called before the after() method, so the system time acquired by the two time will form a time difference, which is the time consumed by the execution of the business method. Through this time difference, we can judge the performance consumption of business methods in unit time. Is it cleverly designed? In fact, almost all system monitoring frameworks on the market are based on such an idea, which can be highly decoupled and reduce code intrusion.
5.2 IModifyService
In order to demonstrate the exception callback notification, we added the function of throwing an exception to the add() method of the previously defined IModifyService interface. See the following code implementation:
package com.tom.spring.demo.service; /** * Add, delete and modify business */ public interface IModifyService { /** * increase */ String add(String name, String addr) throws Exception; /** * modify */ String edit(Integer id, String name); /** * delete */ String remove(Integer id); }
5.3 ModifyService
The code of ModifyService is as follows:
package com.tom.spring.demo.service.impl; import com.tom.spring.demo.service.IModifyService; import com.tom.spring.formework.annotation.GPService; /** * Add, delete and modify business */ @GPService public class ModifyService implements IModifyService { /** * increase */ public String add(String name,String addr) throws Exception { throw new Exception("Deliberately throw an exception to test whether the aspect notification is effective"); // return "modifyService add,name=" + name + ",addr=" + addr; } /** * modify */ public String edit(Integer id,String name) { return "modifyService edit,id=" + id + ",name=" + name; } /** * delete */ public String remove(Integer id) { return "modifyService id=" + id; } }
6. Operation effect demonstration
Enter in the browser http://localhost/web/add.json?name=Tom&addr=HunanChangsha , you can visually and clearly see the exception information thrown by the Service layer, as shown in the following figure.
The console output is shown in the figure below.
Through the console output, we can see that the exception notification successfully captured the exception information, triggered GPMethodBeforeAdvice and GPAfterThrowingAdvice, but did not trigger GPAfterReturningAdvice, which is in line with our expectations. Now do another test and enter http://localhost/web/query.json?name=Tom The results are shown in the figure below:
The console output is shown in the figure below:
Through the console output, we can see that the pre notification and post notification are captured respectively, and the relevant information is printed, which is in line with our expectations.
So far, the AOP module has been completed. Is there a small sense of achievement and eager to try? In the implementation of the whole Mini version, some details are not considered too much. More importantly, we hope to provide "friends" with an idea of learning the source code. Handwritten source code is not to build wheels repeatedly, nor to install "tall". In fact, it is just a learning method we recommend to you. Pay attention to WeChat official account Tom bomb structure and reply to "Spring" to get the complete source code.
This article is the original of "Tom bomb architecture". Please indicate the source for reprint. Technology lies in sharing, I share my happiness!
If this article is helpful to you, you are welcome to pay attention and praise; If you have any suggestions, you can also leave comments or private letters. Your support is the driving force for me to adhere to my creation. Focus on WeChat official account Tom structure, get more dry cargo!
It's not easy to be original. It's cool to insist. I've seen it here. Little partners remember to like, collect and watch it. Pay attention to it three times a button! If you think the content is too dry, you can share and forward it to your friends!