Summary:

Recently encountered in the development of a just can use AOP to achieve the example, on the way to study the implementation of AOP principle, the things learned to a summary. The programming language used in this article is Kotlin, which can be directly converted to Java in IDEA. This article will follow the following table of contents:

  • Introduction of AOP
  • Code implementation examples
  • Implementation principle of AOP
  • Partial source code parsing

1. A brief introduction of AOP

I’m sure you’ve had some experience with AOP and know that it’s faceted programming, and you can find a lot of explanations online. I’ll sum it up in one sentence: AOP allows us to extend functionality horizontally to software without compromising functionality. Then how to understand horizontal expansion? In WEB project development, we usually abide by the principle of three layers, including the Controller layer -> business layer (Service) -> data layer (DAO), then from this structure is vertical, and a specific layer of it is what we call horizontal. Our AOP is all the methods that can be applied to this one horizontal module.

Let’s take a look at the differences between AOP and OOP: AOP is a complement to OOP. When we need to introduce a common behavior for multiple objects, such as logging, action logging, etc., we need to reference the common behavior in each object.

Here are some things you must know about AOP:

  • Facet: Interceptor class, which defines pointcuts and notifications
  • Pointcut: A specific business point intercepted.
  • Notification: a method in the section that declares where the notification method is executed in the target business layer. The notification type is as follows:
    1. Pre-notification: @before is executed Before the target business method is executed
    2. Post-notification: @after is executed After the target business method is executed
    3. Return notification: @AfterRETURNING is executed after the target business method returns a result
    4. Exception notification: @AfterThrowing after the target business method throws an exception
    5. Circular notifications: @around is a powerful replacement for the above four notifications and can control whether and when the target business method executes

2. Implementation examples in the code

Now that I’ve outlined the basics of AOP and the benefits of AOP, how do you implement it in code? I will give you an example: we now have a school management system, which has realized the increase, deletion and change of teachers and students. There is a new requirement, which is to make a record of every increase, deletion and change of teachers and students, and then the principal can check the list of records. So what’s the best way to deal with it? I’ve listed three solutions here, and let’s take a look at the pros and cons.

– The second one is the longest one we use, which is to separate the recording method and call the recording function for other additions, deletions and changes. Obviously, the code duplication is reduced, but such calls still do not reduce the coupling.

– This time we think about the definition of AOP, and then think about our scenario, in fact, we want to do not change the original method of adding, deleting and modifying, to the system to add records of the method, and the role is also a level of method. At this point we can use AOP to implement.

Let’s look at the concrete implementation of the code:

  1. First I define a custom annotation as a pointcut
@Target(AnnotationTarget.FUNCTION)  // The scope of the annotation is declared as a function
@Order(Ordered.HIGHEST_PRECEDENCE)  // Declare the annotation to have the highest priority. If there are multiple annotations, execute this first
annotation class Hanler(val handler: HandlerType)  // Custom annotation class, HandlerType is an enumeration type, which defines the add, delete and modify operations of the student and teacher
Copy the code
  1. The next step is to define the aspect class
@Aspect   // The annotation declares this class to be an aspect class
@Component
class HandlerAspect{

 @Autowired
 private lateinit var handlerService: HandlerService

@AfterReturning("@annotation(handler)")   // When a function annotates the annotation, the method we defined will be executed after the function returns normally
fun hanler(hanler: Hanler) {
    handlerService.add(handler.operate.value)   // Here is how the record is actually executed}}Copy the code
  1. In the end, it’s our original way of doing business
/** * Delete student method */
@Handler(operate= Handler.STUDENT_DELETE)   // When the delete student method is executed, the section class takes effect. When the delete student method is executed, we can see the data generated by the record method
fun delete(id: String) {
   studentService.delete(id)
}
Copy the code

3. AOP implementation principle

Now that we know how to implement it in code, what is the principle behind an AOP implementation? Before I read a blog said, when it comes to AOP, we all know that his implementation principle is dynamic proxy, obviously I did not know before, ha ha, but I believe that you must know reading the article.

When talking about dynamic proxy, we have to say proxy mode. The definition of proxy mode: provide a proxy for an object, and control the reference to the original object by proxy object. The proxy mode contains the following roles: Subject: An abstract topic role, which is an interface. This interface is shared by the object and its proxy; RealSubject: A real topic role, a class that implements an abstract topic interface; Proxy: A Proxy role that contains internal references to real object RealSubject so that it can manipulate real objects. Proxy objects provide the same interface as real objects in order to replace real objects. At the same time, a proxy object can perform operations on the real object with additional operations attached, which is equivalent to encapsulating the real object. As shown below:

So the proxy is divided into static proxy and dynamic proxy, here write two small demo, dynamic proxy is the JDK proxy. For example, students in a class need to hand in their homework, and now the monitor is the agent, so the monitor is the agent, and the students are the object of the agent.

3.1 Static Proxy

First, we create a Person interface. This interface is the common interface between the student (proxyed class) and the class leader (proxyed class), both of whom have the behavior of handing in homework. In this way, students can hand in homework for the monitor to perform.

/** * Created by Mapei on 2018/11/7 * create person interface */
public interface Person {
    / / assignments
    void giveTask();
}
Copy the code

The Student class implements the Person interface, and the Student implements the behavior of handing in assignments.

/** * Created by Mapei on 2018/11/7 */
public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }

    public void giveTask() {
        System.out.println(name + "Hand in Chinese Homework"); }}Copy the code

StudentsProxy class, which implements the Person interface, but also holds a student object that performs the assignment assignment on its behalf.

/** * Created by Mapei on 2018/11/7 ** Created by Mapei on 2018/11/7 ** Created by Mapei on 2018/11/7 ** Created by Mapei on 2018/11/7 ** Created by Mapei on 2018/11/7 ** Created by Mapei on 2018/11/7 ** Created by Mapei on 2018/11/7 **
public class StudentsProxy implements Person{
    // The student being represented
    Student stu;

    public StudentsProxy(Person stu) {
        // Proxy only for student objects
        if(stu.getClass() == Student.class) {
            this.stu = (Student)stu; }}// Proxy assignment calls the behavior of the proxied student
    publicvoid giveTask() { stu.giveTask(); }}Copy the code

Here’s a test to see how the proxy mode works:

/** * Created by Mapei on 2018/11/7 */
public class StaticProxyTest {
    public static void main(String[] args) {
        // The student Lin Qian, whose homework is submitted to the proxy object Monitor to complete
        Person linqian = new Student("Lin light");

        // Generate a proxy object and pass Lin to the proxy object
        Person monitor = new StudentsProxy(linqian);

        // The monitor handed in the homework on behalf of himmonitor.giveTask(); }}Copy the code

Running results:

Instead of performing the assignment directly through Lin (the proxied object), the assignment is performed by the monitor (the proxied object). This is the proxy pattern. The proxy pattern introduces a degree of indirectness in accessing the real object, and by indirectness we mean methods that don’t call the real object directly, so we can add other uses to the proxy process. For example, the monitor wants to tell the teacher that Lin has made great progress recently when he helps Lin to hand in his homework. He can easily do it through the agent mode. Just add the method before the proxy class submits the job. This advantage applies to AOP in Spring, where we can do something before a pointcut and something after a pointcut, which is a method. The class of these methods must be proxied, and some other operation is involved in the proxiation process.

3.2 Dynamic Proxy

The difference between dynamic proxy and static proxy is that the static proxy class is defined by ourselves and is mutated before the program runs, while the dynamic proxy class is created while the program runs. The advantage of dynamic proxies over static proxies is that it is easy to uniformly handle the functions of proxy classes without changing the methods in each proxy class. For example, if we want to add a handler to each proxy method, we have only one proxy method in the example above. If there are many proxy methods, it will be too much trouble. Let’s look at how dynamic proxy is implemented.

First, define a Person interface:

/** * Created by Mapei on 2018/11/7 * create person interface */
public interface Person {
    / / assignments
    void giveTask();
}
Copy the code

The next step is to create the actual class that needs to be proxied, namely the student class:

/** * Created by Mapei on 2018/11/7 */
public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }

    public void giveTask() {
        System.out.println(name + "Hand in Chinese Homework"); }}Copy the code

Create a StuInvocationHandler class that implements the InvocationHandler interface. This class holds an instance target of the proxied object. There is an Invoke method in InvocationHandler, and all methods that execute a proxy object are replaced with invoke methods.

/** * Created by Mapei on 2018/11/7 */
public class StuInvocationHandler<T> implements InvocationHandler {
    //invocationHandler holds the proxied object
    T target;

    public StuInvocationHandler(T target) {
        this.target = target;
    }

    /** * proxy: represents the dynamic proxy object * method: represents the method being executed * args: represents the argument passed in when calling the target method */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Proxy Execution" +method.getName() + "Method");
        Object result = method.invoke(target, args);
        returnresult; }}Copy the code

Now we can create the proxy object in detail.

/** * Created by Mapei on 2018/11/7 * proxy */
public class ProxyTest {
    public static void main(String[] args) {

        // Create an instance object, which is the proxied object
        Person linqian = new Student("Lin light");

        // Create an InvocationHandler associated with the proxy object
        InvocationHandler stuHandler = new StuInvocationHandler<Person>(linqian);

        // Create a stuProxy object to invoke Linqian. Each Invocation method of the stuProxy object replaces the Invoke method from the Invocation Invocation
        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<? >[]{Person.class}, stuHandler);

        // The agent executes the method of submitting the jobstuProxy.giveTask(); }}Copy the code

We execute the proxy test class. First we create a student to be proxied and pass it into stuHandler. When we create the proxy object stuProxy, we pass stuHandler as a parameter. Finally, the invoke method in StuInvocationHandler is executed. So it’s no surprise to see the results below.

The invoke method of the InvocationHandler is used to execute the method. With this in mind, we need to take a look at the source code of the dynamic proxy and conduct a simple analysis of it.

Create a dynamic Proxy object using the newProxyInstance method of the Proxy class.

publicstatic Object newProxyInstance(ClassLoader loader, Class<? >[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h);finalClass<? >[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();
        if(sm ! =null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /* * Look up or generate the designated proxy class. */Class<? > cl = getProxyClass0(loader, intfs);/* * Invoke its constructor with the designated invocation handler. */
        try {
            if(sm ! =null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            finalConstructor<? > cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;
            if(! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void> () {public Void run() {
                        cons.setAccessible(true);
                        return null; }}); }return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                thrownew InternalError(t.toString(), t); }}catch (NoSuchMethodException e) {
            thrownew InternalError(e.toString(), e); }}Copy the code

Then, we need to focus on Class<? > < span style = “box-sizing: border-box; color: RGB (51, 51, 51); font-size: 14px! Important; white-space: inherit! Important;”

        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Student.class.getInterfaces());
        String path = "/Users/mapei/Desktop/okay/65707.class";

        try{
            FileOutputStream fos = new FileOutputStream(path);
            fos.write(classFile);
            fos.flush();
            System.out.println("Proxy class file written successfully");
        }catch (Exception e) {
            System.out.println("Write file error");
        }
Copy the code

Decompiling this class file, let’s see what the JDK generates for us:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;

public final class $Proxy0 extends Proxy implements Person
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  /** * Call InvocationHandler (); /** * Call InvocationHandler (); /** * Call InvocationHandler (); The InvocationHandler in turn holds an instance of the * proxied object and can call the actual object instance. * /
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  // This static block was originally at the end, so I brought it to the front to describe it
   static
  {
    try
    {
      // Look at the name M3 reflected by giveTask in the static block here, forget the rest
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object")}); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("proxy.Person").getMethod("giveTask", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      thrownew NoClassDefFoundError(localClassNotFoundException.getMessage()); }}/** ** This calls the giveMoney method of the proxy object, directly invoking the Invoke method of InvocationHandler and passing m3 in. *this.h.invoke(this, m3, null); We can think of InvocationHandler as a mediation class that holds a proxied object and invokes the proxied object's corresponding method in the Invoke method. References to the proxied object are held in an aggregate manner, and external calls to Invoke are eventually converted to calls to the proxied object. * /
  public final void giveTask()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      thrownew UndeclaredThrowableException(localThrowable); }}}Copy the code

Having looked at the source code for dynamic proxy, we are going to look at the source code for AOP implementation in Spring.

4. Partial source code analysis

Aop to create proxy source analysis

  1. How can beans be wrapped as proxies
       	protectedObject createProxy( Class<? > beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
   		AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
   	}

       // create proxyFactory, proxy production is mainly done in proxyFactory
   	ProxyFactory proxyFactory = new ProxyFactory();
   	proxyFactory.copyFrom(this);

   	if(! proxyFactory.isProxyTargetClass()) {if (shouldProxyTargetClass(beanClass, beanName)) {
   			proxyFactory.setProxyTargetClass(true);
   		}
   		else{ evaluateProxyInterfaces(beanClass, proxyFactory); }}// 2. Rewrap the advice for the current bean as Advisor class and add it to ProxyFactory
   	Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
   	for (Advisor advisor : advisors) {
   		proxyFactory.addAdvisor(advisor);
   	}

   	proxyFactory.setTargetSource(targetSource);
   	customizeProxyFactory(proxyFactory);

   	proxyFactory.setFrozen(this.freezeProxy);
   	if (advisorsPreFiltered()) {
   		proxyFactory.setPreFiltered(true);
   	}

       // 3. Call getProxy to get the proxy of the bean
   	return proxyFactory.getProxy(getProxyClassLoader());
   }
Copy the code
  1. What type of Proxy is created? JDKProxy or CGLIBProxy?
	public Object getProxy(ClassLoader classLoader) {
		return createAopProxy().getProxy(classLoader);
	}
    The createAopProxy() method determines what type of proxy to create
	protected final synchronized AopProxy createAopProxy() {
		if (!this.active) {
			activate();
		}
        CreateAopProxy ()
		return getAopProxyFactory().createAopProxy(this);
	}
	
    // createAopProxy()
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        // 1. Config.isoptimize () Whether to use the optimized agent strategy, currently used with CGLIB
        / / config. IsProxyTargetClass () if the target class itself is agent instead of the target class interface
        / / hasNoUserSuppliedProxyInterfaces () whether there is any agent interface
		if(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<? > targetClass = config.getTargetClass();if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
            
            // 2. If the target class is an interface class (the target object implements the interface), use JDKproxy directly
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
            
            // 3. In other cases, use CGLIBproxy
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			returnnew JdkDynamicAopProxy(config); }}Copy the code
  1. GetProxy () method
   final class JdkDynamicAopProxy implements AopProxy.InvocationHandler.Serializable// JdkDynamicAopProxy class structure, which implements InvocationHandler, there must bean invoke method to be invoked, that is, when the user calls a bean-related method() is actually called// getProxy()
   public Object getProxy(ClassLoader classLoader) {
   	if (logger.isDebugEnabled()) {
   		logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); } Class<? >[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
   	findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
       
       // JDK proxy Standard usage of dynamic proxy
   	return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
   }
Copy the code
  1. Invoke () method
    Invoke (); invoke(); invoke(); // Invoke ();
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		MethodInvocation invocation;
		Object oldProxy = null;
		boolean setProxyContext = false;
 
		TargetSource targetSource = this.advised.targetSource; Class<? > targetClass =null;
		Object target = null;
 
		try {
            // Check whether method is an equals, hashCode, etc method
			if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
				// The target does not implement the equals(Object) method itself.
				return equals(args[0]);
			}
			else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				// The target does not implement the hashCode() method itself.
				return hashCode();
			}
			else if (method.getDeclaringClass() == DecoratingProxy.class) {
				// There is only getDecoratedClass() declared -> dispatch to proxy config.
				return AopProxyUtils.ultimateTargetClass(this.advised);
			}
			else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
					method.getDeclaringClass().isAssignableFrom(Advised.class)) {
				// Service invocations on ProxyConfig with the proxy config...
				return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
			}
 
			Object retVal;
 
			if (this.advised.exposeProxy) {
				// Make invocation available if necessary.
				oldProxy = AopContext.setCurrentProxy(proxy);
				setProxyContext = true;
			}
 
			// May be null. Get as late as possible to minimize the time we "own" the target,
			// in case it comes from a pool.
			target = targetSource.getTarget();
			if(target ! =null) {
				targetClass = target.getClass();
			}
			// 2. Get the list of blocked methods for the current bean
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
 
			// 3. If null, call target method directly
			if (chain.isEmpty()) {
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
            Proceed (); proceed (); // Proceed ()
			else {
				// We need to create a method invocation...
				invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// Proceed to the joinpoint through the interceptor chain.retVal = invocation.proceed(); }...returnretVal; }}}Copy the code

So here, I want to say the content is almost over, if there is anything wrong, or there is any doubt, welcome to give advice!