Before we begin our formal introduction to aop modules, we need to understand some basic terminology concepts.

In the software industry, AOP for the abbreviation of Aspect Oriented Programming, meaning: section-oriented Programming, through pre-compilation and runtime dynamic proxy to achieve unified maintenance of program functions of a technology. AOP is a continuation of OOP, a hot topic in software development, and an important content in Spring framework. It is a derivative paradigm of functional programming.

Using AOP, each part of the business logic can be isolated, thus reducing the degree of coupling between each part of the business logic, improving the reusability of the program, and improving the efficiency of development.

What problems can AOP be used to solve?

Code reuse, common function extraction, simplified development, decoupling between businesses; The most typical example is logging, which tends to span every business module in a system, and AOP is a good way to isolate it.

There are several AOP framework technologies available:

  • AspectJ: A weaver based on source code and bytecode unpacking that users need when using a new language, so Spring’s Aop encapsulates AspectJ.
  • AspectWerkz: AOP framework that uses bytecode dynamic weaving and XML configuration
  • Jboss-aop: An AOP framework based on interceptors and metadata, running on the JBoss application server, and some of the related technology implementations used in AOP
  • Javassist: Java bytecode manipulation class library, a subproject of JBoss

Basic concepts of AOP

Before introducing AOP technology, let’s clarify a few basic conceptual points:

Aspect (cut)

Can be thought of as a class that extracts business code, for example:

@Aspect public class LogAspect { /** ... This is part of the relevant methods omitted most of the content... / * *}Copy the code

JoinPoint

The intercept point can be understood as the following parameter:

The Spring framework currently only supports method-level interception, and AOP join points can be used in many ways, such as arguments, constructors, and so on.

PointCut

It can be understood as intercepting a definition for each join point.

Advice (notice)

Refers to the code that needs to be executed after intercepting the join point.

There are pre, post, exception, surround, and end

The specific performance is as follows:

The target object

The target object of the proxy

Weave in

The process of applying the aspect to the target object and causing the proxy object to be created. Weave is a process.

Introduction

Introduce methods or fields that can be dynamically initialized at run time without modifying the code.

How does Cglib implement proxy for interface calls

First we define a basic business code object:

package org.idea.spring.aop.cglib; /** * @author linhao * @date created in 3:56 PM 2021/5/6 */ public class MyBis {void doBus(){system.out.println ("this ") is do bis"); }}Copy the code

This is followed by intercepting the target object:

package org.idea.spring.aop.cglib; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * the whole process of a call is: * @author linhao * @date created in 3:57 PM */ public class TargetInterceptor implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("==== intercept before ===="); // The value returned from the proxy instance's method call. Object result = methodProxy.invokeSuper(o,objects); System.out.println("==== intercept after ===="); return result; }}Copy the code

Finally, the execution of the test code.

package org.idea.spring.aop.cglib; import net.sf.cglib.core.DebuggingClassWriter; import net.sf.cglib.proxy.Enhancer; Public class TestCglib {public static void main(String[]) public static void main(String[]) Args) throws InterruptedException {// Store the bytecode file generated by cglib in this directory. Let's see what's there System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/idea/IdeaProjects/framework-project/spring-frame work/spring-core/spring-aop/cglib-class"); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(MyBis.class); enhancer.setCallback(new TargetInterceptor()); MyBis myBis = (MyBis) enhancer.create(); myBis.doBus(); }}Copy the code

Execution Result:

The Intercept method in TargetInterceptor in the above code automatically calls back the target function after it has been called to achieve the effect of a proxy call.

Cglib and JDK agents

Cglib’s proxy mode is quite different from the JDK’s implementation of the proxy technology. The JDK requires that the proagent object implement the JDK’s InvocationHandler interface to perform interface callbacks. However, cglib does not enforce the implementation of the interface. It is also much more efficient than the JDK’s own proxy.

Principle of Cglib proxy

I can only briefly introduce the principle of CGLIb. There are so many points in it that it is easy to fall into a pit if you dig deeply into it, so I’m going to use some plain words to introduce it.

Cglib implements the basic idea of proxy

1. Wrap the object to be called and index the method.

2. When calling the target method, use the index value to find and call the function.

Check out this blog post for more details:

www.cnblogs.com/cruze/p/386…

Along the lines of this blog post, I implemented a cglib-like proxy tool myself. Code address at bottom

Difficult points:

How do I index a method? How do I call a function based on an index?

Here I post some of my own thinking and output.

Fetch hashcode from the call to the method name, and then use the switch keyword to determine the call to the function name:

It works pretty much the same, but a lot of the details aren’t perfect:

The main purpose of using cglib to implement the proxy function is to call some methods before executing some functions. And in order to do that, you can actually do it with reflection. But reflection has a high performance overhead over multiple calls. The main optimization cglib makes in this area is to wrap the calling method with an index, produce new bytecode, and achieve performance improvement.

The implementation of the repository address can be found at the end of this article.

How does Cglib generate bytecode files underneath

ASM

For requirements that require manual manipulation of bytecodes, YOU can use ASM, which can either directly produce.class bytecodes files or dynamically modify class behavior before classes are loaded into the JVM. Application scenarios for ASM include AOP (Cglib is based on ASM), hot deployment, modifying classes in other JARS, and so on.

The overall operation flow chart is as follows:

First read the compiled.class file through ClassReader

The bytecode is modified through a Visitor pattern (Visitor). Common Visitor classes include MethodVisitor to modify methods, or FieldVisitor to modify variables, and so on

Use ClassWriter to rebuild and compile the modified bytecode file, or output the modified bytecode file to the file

How do I implement a simple version of AOP myself

First you need to define the relevant annotations:

package org.idea.spring.aop.version1.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author linhao * @date created in 3:49 PM 2021/5/6 */ @retention (value = retentionPolicy.runtime) @Target(ElementType.METHOD) public @interface Pointcut { String value() default ""; } package org.idea.spring.aop.version1.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author linhao * @date Created in 3:42 PM 2021/5/6 */ @retention (retentionPolicy.runtime) @Target(ElementType.METHOD) public @interface Before { String value(); } package org.idea.spring.aop.version1.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author linhao * @date Created in 3:30 PM 2021/5/6 */ @retention (retentionPolicy.runtime) @Target(ElementType.METHOD) public @interface After { String value(); } package org.idea.spring.aop.version1.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author linhao * @date Created in 3:41 PM 2021/5/6 */ @retention (retentionPolicy.runtime) @Target(ElementType.TYPE) public @interface Aspect { String value() default ""; }Copy the code

Then add to the annotations you define and combine them into an Aspect:

package org.idea.spring.aop.version1.aspect; import org.idea.spring.aop.version1.annotation.After; import org.idea.spring.aop.version1.annotation.Aspect; import org.idea.spring.aop.version1.annotation.Before; import org.idea.spring.aop.version1.annotation.Pointcut; import java.lang.reflect.Method; /** * @author linhao * @date created in 3:43 PM 2021/5/6 */ @aspect public class MyAspect { @Pointcut("org.idea.spring.aop.version1.test.*.*(..) ") public void pointCut(){ } @Before("pointCut()") public void doBefore(Method method, Object object){ System.out.println("doBefore"); } @After("pointCut()") public void doAfter(Method method, Object object){ System.out.println("doAfter"); }}Copy the code

Also add a method for testing

package org.idea.spring.aop.version1.test; /** * @author linhao * @date created in 3:44 PM 2021/5/6 */ public class TestMethod {public void doTest(){ System.out.println("do test"); }}Copy the code

Finally, a kernel of AspectLoader code

package org.idea.spring.aop.version1; import com.google.common.collect.ImmutableSet; import com.google.common.reflect.ClassPath; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import org.idea.spring.aop.version1.annotation.After; import org.idea.spring.aop.version1.annotation.Aspect; import org.idea.spring.aop.version1.annotation.Before; import org.idea.spring.aop.version1.annotation.Pointcut; import org.idea.spring.aop.version1.test.TestMethod; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author linhao * @date created in 3:51 PM 2021/5/6 */ public class AspectLoader {/** * configure to scan aop aspect base package path */ public static final String PACKAGE_NAME = "org.idea.spring.aop"; Public Map<String, Object> beanContainer = new HashMap<>(); public AspectLoader() { this.beanContainer.put("TestMethod", new TestMethod()); } public static void main(String[] args) { AspectLoader aspectLoader = new AspectLoader(); aspectLoader.init(); TestMethod testMethod = (TestMethod) aspectLoader.beanContainer.get("TestMethod"); testMethod.doTest(); } / initialization aop configuration related to * * * * / private void init () {try {/ / get cut some aspect List < Class > targetsWithAspectJAnnotationList = this.getAspectClass(); for (Class targetsWithAspectJAnnotation : targetsWithAspectJAnnotationList) { Method beforeMethod = this.getBeforeMethod(targetsWithAspectJAnnotation); Pointcut pointcut = (Pointcut) this.getMethodAnnotation(targetsWithAspectJAnnotation, Pointcut.class); Method afterMethod = this.getAfterMethod(targetsWithAspectJAnnotation); List<Class> classList = this.getClassFromPackage(AspectLoader.class, pointcut.value().substring(0, pointcut.value().indexOf("*") - 1)); for (Class sourceClass : classList) { Object aspectObject = targetsWithAspectJAnnotation.newInstance(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(sourceClass); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { beforeMethod.invoke(aspectObject, method, obj); methodProxy.invokeSuper(obj, objects); afterMethod.invoke(aspectObject,method,obj); return obj; }}); Object proxyObj = enhancer.create(); this.beanContainer.put(sourceClass.getSimpleName(), proxyObj); } } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } } private List<Class> getAspectClass() throws ClassNotFoundException, IOException { final ClassPath classPath = ClassPath.from(AspectLoader.class.getClassLoader()); List<Class> aspectClass = new ArrayList<>(); ImmutableSet<ClassPath.ClassInfo> clazz = classPath.getAllClasses(); List<ClassPath.ClassInfo> list = clazz.asList(); for (ClassPath.ClassInfo classInfo : list) { if (classInfo.getName() ! = null && classInfo.getPackageName().contains(PACKAGE_NAME)) { Class clazzTemp = Class.forName(classInfo.getName()); if (clazzTemp.isAnnotationPresent(Aspect.class)) { aspectClass.add(clazzTemp); } } } return aspectClass; @param source @Param packageName @return @throws Exception */ private List<Class> getClassFromPackage(Class source, String packageName) { List<Class> classList = new ArrayList<>(); final ClassPath classPath; try { classPath = ClassPath.from(source.getClassLoader()); ImmutableSet<ClassPath.ClassInfo> clazz = classPath.getAllClasses(); List<ClassPath.ClassInfo> list = clazz.asList(); for (ClassPath.ClassInfo classInfo : list) { if (classInfo.getName() ! = null && classInfo.getPackageName().contains(packageName)) { classList.add(Class.forName(classInfo.getName())); } } } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } return classList; } private Annotation getMethodAnnotation(Class source, Class annotationClass) { Method[] methods = source.getDeclaredMethods(); for (Method method : methods) { if (method.isAnnotationPresent(annotationClass)) { Annotation[] beforeArr = method.getAnnotationsByType(annotationClass); if (beforeArr.length > 0) { return beforeArr[0]; } } } return null; } private Method getBeforeMethod(Class source) { Method[] methods = source.getDeclaredMethods(); for (Method method : methods) { if (method.isAnnotationPresent(Before.class)) { return method; } } return null; } private Method getAfterMethod(Class source) { Method[] methods = source.getDeclaredMethods(); for (Method method : methods) { if (method.isAnnotationPresent(After.class)) { return method; } } return null; }}Copy the code