Spring Series: Spring AOP Details

I. What is AOP

AOP (Face Oriented Programming) is a kind of programming thought, in which Spring AOP and AspectJ are both realistic programming ideas.In contrast to OOP (process-oriented programming), there is another way to program. For the cross-cutting problems that arise during OOP, these cross-cutting issues are business-independent and can be achieved through precompilation and run-time dynamic agents.For example, they can be used in logging, performance monitoring, transaction management, and so on.

2. Basic concepts of AOP

Aspect: Usually a class that defines a tangent point and a notification, and in Spring AOP you can label this class as a tangent with @AspectJ;

Join point: Can be understood as a method in the target object, which is the method to be enhanced, that is, a starting point for what we want to do;

Pointcut: A point of tangency can be understood as a set of connection points;

Target object: The proxy object, that is, the target object;

AOP proxy: weave the proxied object into the enhanced object;

Weaving: Enhancement is the process of adding proxy logic to the target object;

Advice: Used to specify the enhanced location at a specific connection point;

Before advice: Call the notification before the target method is called;

(2) After returning advice: invoke notification after the successful execution of the target method;

(3) After throwing advice: invoke notification after the target method throws an exception;

(4) After (finally) advice: invoke notification after the target method completes (whether an exception occurs or not, in finally);

_Around advice: notifications around connection points (target methods).Custom behavior can be performed before and after method calls;

3. Realization of Agent

We know that AOP can be implemented by precompiled and runtime dynamic agents, so what are the implementation methods of agents?

We define an interface class: UserService

package com.toby.service;

/**
 * @desc: user Business Interface Class
 * @author: toby
 * @date: 2019/8/4 23:28
 */
public interface UserService {
    /**
     * Add to
     */
    void add();
    
    /**
     * say hello
     * @param name
     * @return
     */
    String say(String name);
}

In defining an implementation class UserServiceImpl

package com.toby.service.impl;

import com.toby.service.UserService;
import org.springframework.stereotype.Service;

/**
 * @desc: user Business implementation class
 * @author: toby
 * @date: 2019/8/4 23:29
 */
@Service
public class UserServiceImpl implements UserService {

    @Override
    public void add() {
        System.out.println("implement UserServiceImpl Of add Method");
    }

    @Override
    public String say(String name) {
        System.out.println("implement UserServiceImpl Of say Method args = " + name);
        return "hello " + name;
    }
}

First: static proxy

Define a static proxy class that implements the UserService interface:

package com.toby.proxy;

import com.toby.service.UserService;

/**
 * @desc: Static Proxy
 * @author: toby
 * @date: 2019/8/4 23:30
 */
public class StaticProxy implements UserService {

    private UserService userService;

    public StaticProxy(UserService userService){
        this.userService = userService;
    }

    @Override
    public void add() {
        System.out.println("Add Log Start");
        userService.add();
        System.out.println("Add Log End");
    }

    @Override
    public String say(String name) {
        return "";
    }
}

Second: Jdk dynamic proxy

package com.toby.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @desc: JDK Dynamic proxy implements an interface: InvocationHandler
 * JDK A dynamic proxy mechanism can only proxy classes that implement interfaces, but it cannot implement a dynamic proxy for JDK without a class that implements interfaces
 * @author: toby
 * @date: 2019/8/4 23:34
 */
public class JdkDynamicProxy implements InvocationHandler {
    /**
     * Target object
     */
    private Object targetObject;

    public Object createJdkProxy(final Object targetObject){
        this.targetObject = targetObject;
        return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
                this.targetObject.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //Write corresponding enhancement code
        System.out.println("Jdk Logging Start");
        //Call real business methods
        Object obj = method.invoke(this.targetObject,args);
        System.out.println("Jdk Logging End");
        return obj;
    }
}

Third: Cglib dynamic proxy

package com.toby.proxy;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @desc: CGLIB The dynamic proxy cglib implements proxy for classes by generating a subclass of the specified target class.
 * It also overrides the method enhancements, but because inheritance is used, final-modified classes cannot be proxied
 * @author: toby
 * @date: 2019/8/4 23:43
 */
public class CglibDynamicProxy implements MethodInterceptor {

    /**
     * Target object
     */
    private Object targetObject;

    /**
     * Create Cglib dynamic proxy
     * @param targetObject
     * @return
     */
    public Object createCglibDynamicProxy(final Object targetObject){
        this.targetObject = targetObject;
        //Cglib Core object in, which is used to generate proxy objects
        Enhancer enhancer = new Enhancer();
        //Specify the delegate class, that is, the target object is the parent class
        enhancer.setSuperclass(this.targetObject.getClass());
        //Using a proxy requires a corresponding proxy object
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLIB Logging Start");
        //The delegate class becomes a parent.Call the real service provider
        Object obj = methodProxy.invoke(this.targetObject,args);
        System.out.println("CGLIB Logging End");
        return obj;
    }
}

The implementation of the JDK proxy is based on the interface. The proxy class inherits Proxy and implements the interface.CGLIB inherits the class being proxied to implement it; that's why JDK dynamic proxies need to implement interfaces?Java is single inheritance

The following defines a byte code generator, ByteCodeGenerator, to see what happens:

package com.toby.proxy.generator;

import com.toby.service.impl.UserServiceImpl;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import sun.misc.ProxyGenerator;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Modifier;
import java.nio.file.Files;

/**
 * @desc: Byte Code Generator
 * @author: toby
 * @date: 2019/8/5 0:05
 */
public class ByteCodeGenerator {
    /**
     * Generate byte code from target object (Jdk)
     * @param target
     * @param <T>
     * @return
     */
    public static <T> byte[] generatorByteCodeByJdkProxy(T target){
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
        byte [] codes = ProxyGenerator.generateProxyClass("Proxy$"+target.getClass().getName(), target.getClass().getInterfaces(),accessFlags);
        return codes;
    }

    /**
     * Generate byte code from target object (Cglib)
     * @param target
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> byte[] generatorByteCodeByCglib(final T target) throws Exception {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invoke(target,objects));
        enhancer.create();
        byte [] codes = enhancer.getStrategy().generate(enhancer);
        return codes;
    }

    public static void main(String[] args) {
        /**
         * Test jdk
         */
        try {
            byte [] codes = ByteCodeGenerator.generatorByteCodeByJdkProxy(new UserServiceImpl());
            File file = new File(System.getProperty("user.dir")+"/spring-aop/target/Proxy$UserServiceImpl.class");
            Files.write(file.toPath(),codes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        /**
         * Testing cglib
         */
        try {
            FileOutputStream out = new FileOutputStream(System.getProperty("user.dir")+"/spring-aop/target/Cglib$UserServiceImpl.class");
            out.write(ByteCodeGenerator.generatorByteCodeByCglib(new UserServiceImpl()));
            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Dynamic proxy byte code generated by Jdk:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package Proxy$com.toby.service.impl;

import com.toby.service.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class UserServiceImpl extends Proxy implements UserService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public UserServiceImpl(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void add() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.toby.service.UserService").getMethod("add");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

Cglib generates dynamic proxy byte codes:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.toby.service.impl;

import java.lang.reflect.Method;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

public class UserServiceImpl$$EnhancerByCGLIB$$b26297df extends UserServiceImpl implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$add$0$Method;
    private static final MethodProxy CGLIB$add$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$equals$1$Method;
    private static final MethodProxy CGLIB$equals$1$Proxy;
    private static final Method CGLIB$toString$2$Method;
    private static final MethodProxy CGLIB$toString$2$Proxy;
    private static final Method CGLIB$hashCode$3$Method;
    private static final MethodProxy CGLIB$hashCode$3$Proxy;
    private static final Method CGLIB$clone$4$Method;
    private static final MethodProxy CGLIB$clone$4$Proxy;

    static void CGLIB$STATICHOOK2() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.toby.service.impl.UserServiceImpl$$EnhancerByCGLIB$$b26297df");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$1$Method = var10000[0];
        CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
        CGLIB$toString$2$Method = var10000[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
        CGLIB$hashCode$3$Method = var10000[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
        CGLIB$clone$4$Method = var10000[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
        CGLIB$add$0$Method = ReflectUtils.findMethods(new String[]{"add", "()V"}, (var1 = Class.forName("com.toby.service.impl.UserServiceImpl")).getDeclaredMethods())[0];
        CGLIB$add$0$Proxy = MethodProxy.create(var1, var0, "()V", "add", "CGLIB$add$0");
    }

    final void CGLIB$add$0() {
        super.add();
    }

    public final void add() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$add$0$Method, CGLIB$emptyArgs, CGLIB$add$0$Proxy);
        } else {
            super.add();
        }
    }

    final boolean CGLIB$equals$1(Object var1) {
        return super.equals(var1);
    }

    public final boolean equals(Object var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            Object var2 = var10000.intercept(this, CGLIB$equals$1$Method, new Object[]{var1}, CGLIB$equals$1$Proxy);
            return var2 == null ? false : (Boolean)var2;
        } else {
            return super.equals(var1);
        }
    }

    final String CGLIB$toString$2() {
        return super.toString();
    }

    public final String toString() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? (String)var10000.intercept(this, CGLIB$toString$2$Method, CGLIB$emptyArgs, CGLIB$toString$2$Proxy) : super.toString();
    }

    final int CGLIB$hashCode$3() {
        return super.hashCode();
    }

    public final int hashCode() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            Object var1 = var10000.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy);
            return var1 == null ? 0 : ((Number)var1).intValue();
        } else {
            return super.hashCode();
        }
    }

    final Object CGLIB$clone$4() throws CloneNotSupportedException {
        return super.clone();
    }

    protected final Object clone() throws CloneNotSupportedException {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? var10000.intercept(this, CGLIB$clone$4$Method, CGLIB$emptyArgs, CGLIB$clone$4$Proxy) : super.clone();
    }

    public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
        String var10000 = var0.toString();
        switch(var10000.hashCode()) {
        case -1422568652:
            if (var10000.equals("add()V")) {
                return CGLIB$add$0$Proxy;
            }
            break;
        case -508378822:
            if (var10000.equals("clone()Ljava/lang/Object;")) {
                return CGLIB$clone$4$Proxy;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
                return CGLIB$equals$1$Proxy;
            }
            break;
        case 1913648695:
            if (var10000.equals("toString()Ljava/lang/String;")) {
                return CGLIB$toString$2$Proxy;
            }
            break;
        case 1984935277:
            if (var10000.equals("hashCode()I")) {
                return CGLIB$hashCode$3$Proxy;
            }
        }

        return null;
    }

    public UserServiceImpl$$EnhancerByCGLIB$$b26297df() {
        CGLIB$BIND_CALLBACKS(this);
    }

    public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
        CGLIB$THREAD_CALLBACKS.set(var0);
    }

    public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
        CGLIB$STATIC_CALLBACKS = var0;
    }

    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        UserServiceImpl$$EnhancerByCGLIB$$b26297df var1 = (UserServiceImpl$$EnhancerByCGLIB$$b26297df)var0;
        if (!var1.CGLIB$BOUND) {
            var1.CGLIB$BOUND = true;
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();
            if (var10000 == null) {
                var10000 = CGLIB$STATIC_CALLBACKS;
                if (var10000 == null) {
                    return;
                }
            }

            var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
        }

    }

    public Object newInstance(Callback[] var1) {
        CGLIB$SET_THREAD_CALLBACKS(var1);
        UserServiceImpl$$EnhancerByCGLIB$$b26297df var10000 = new UserServiceImpl$$EnhancerByCGLIB$$b26297df();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Callback var1) {
        CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
        UserServiceImpl$$EnhancerByCGLIB$$b26297df var10000 = new UserServiceImpl$$EnhancerByCGLIB$$b26297df();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
        CGLIB$SET_THREAD_CALLBACKS(var3);
        UserServiceImpl$$EnhancerByCGLIB$$b26297df var10000 = new UserServiceImpl$$EnhancerByCGLIB$$b26297df;
        switch(var1.length) {
        case 0:
            var10000.<init>();
            CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
            return var10000;
        default:
            throw new IllegalArgumentException("Constructor not found");
        }
    }

    public Callback getCallback(int var1) {
        CGLIB$BIND_CALLBACKS(this);
        MethodInterceptor var10000;
        switch(var1) {
        case 0:
            var10000 = this.CGLIB$CALLBACK_0;
            break;
        default:
            var10000 = null;
        }

        return var10000;
    }

    public void setCallback(int var1, Callback var2) {
        switch(var1) {
        case 0:
            this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
        default:
        }
    }

    public Callback[] getCallbacks() {
        CGLIB$BIND_CALLBACKS(this);
        return new Callback[]{this.CGLIB$CALLBACK_0};
    }

    public void setCallbacks(Callback[] var1) {
        this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
    }

    static {
        CGLIB$STATICHOOK2();
    }
}

Fourth: Javassist dynamic proxy (detailed in Dynamic Byte Code Insertion)

(1) Define a JavassistDynamicProxy to implement Javassist dynamic proxy:

package com.toby.proxy;

import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;

/**
 * @desc: javassist
 * @author: toby
 * @date: 2019/8/15 20:22
 */
public class JavassistDynamicProxy {

    /**
     * Create Javassist dynamic proxy
     * @param targetObject
     * @throws Exception
     * @return
     */
    public Object createJavassistDynamicProxy(final Object targetObject)throws Exception {
        ProxyFactory factory = new ProxyFactory();
        factory.setInterfaces(targetObject.getClass().getInterfaces());
        Class<?> proxyClass = factory.createClass();
        Object javassistProxy = proxyClass.newInstance();
        ((ProxyObject)javassistProxy).setHandler((self,thisMethod,proceed,args)-> {
            //Write corresponding enhancement code
            System.out.println("Javassist Logging Start");
            //Call real business methods
            Object obj = thisMethod.invoke(targetObject,args);
            System.out.println("Javassist Logging End");
            return obj;
        });
        return javassistProxy;
    }
}

(2) Define a JavassistBytecodeDynamicProxy to implement Javassist dynamic proxy:

package com.toby.proxy;

import javassist.*;

import java.lang.reflect.Field;

/**
 * @desc: javassist Byte Code Dynamic Proxy
 * @author: toby
 * @date: 2019/8/15 20:42
 */
public class JavassistBytecodeDynamicProxy {

    /**
     * Create Javassist Byte Code Dynamic Proxy
     * @param targetObject
     * @return
     * @throws Exception
     */
    public static Object createJavassistBytecodeDynamicProxy(final Object targetObject) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass proxyClass = pool.makeClass("JavassistProxy" +  "&" +targetObject.getClass().getName());
        proxyClass.addInterface(pool.get(targetObject.getClass().getInterfaces()[0].getName()));
        proxyClass.addConstructor(CtNewConstructor.defaultConstructor(proxyClass));
        proxyClass.addField(CtField.make("private " + targetObject.getClass().getName() + " targetObject;", proxyClass));
        proxyClass.addMethod(CtNewMethod.make("public void add() { \n" +
                "System.out.println(\"Javassist Byte Code Logging Start\");\n" +
                "targetObject.add();\n" +
                "System.out.println(\"Javassist End of Byte Code Logging\");\n"+
                "}", proxyClass));
        Class<?> clazz = proxyClass.toClass();
        Object bytecodeProxy = clazz.newInstance();
        Field field = bytecodeProxy.getClass().getDeclaredField("targetObject");
        field.setAccessible(true);
        field.set(bytecodeProxy,targetObject);
        return bytecodeProxy;
    }

    /**
     * Create Javassist Byte Code Dynamic Proxy 2
     * @param targetObject
     * @return
     * @throws Exception
     */
    public static Object createJavassistBytecodeDynamicProxy2(final Object targetObject) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.appendSystemPath();
        CtClass ctl = pool.get(targetObject.getClass().getName());
        ctl.setName("JavassistProxy" +  "&" + targetObject.getClass().getName());
        CtMethod ctMethod = ctl.getDeclaredMethod("add");
        ctMethod.insertBefore("System.out.println(\"Javassist Byte Code 2 Logging Start\");");
        ctMethod.insertAfter("System.out.println(\"Javassist Byte Code 2 Logging End\");");
        Class<?> clazz = ctl.toClass();
        Object bytecodeProxy = clazz.newInstance();
        return bytecodeProxy;
    }
}

4. Spring AOP

Spring AOP offers two programming styles, detailed usage on Spring's website: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-aspectj-support

(1) @AspectJ support (using aspectj's comment)

(2) Schema-based AOP support (based on xml aop:config namespace)

Enable @AspectJ support

(1) Enable @AspectJ support using Java Configuration: To enable @AspectJ support using Java @Configuration, add the @EnableAspectJAutoProxy comment:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

(2) Enable @AspectJ support using XML configuration: To enable @AspectJ support using XML-based configuration, use the aop:aspectj-autoproxy element

<aop:aspectj-autoproxy/>

Declare an Aspect

@Aspect
@Component
public class UserAspect {
}

Declare a Pointcut

@Pointcut("execution(* com.toby.service.UserService.*(..))")//the pointcut expression
public void pointCutExecution(){}//the pointcut signature

9 entry point expressions supported by Spring AOP

Execution:execution is used to match methods to execute join points, minimum granularity method

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
The question mark here indicates that the current item may or may not have the following semantics
modifiers-pattern: Method visibility, such as public, protected;
ret-type-pattern: The return value type of the method, such as int, void, and so on;
declaring-type-pattern: The full path name of the class in which the method resides, such as com.spring.Aspect;
name-pattern: Method name type, such as buisinessService();
param-pattern: The parameter type of the method, such as java.lang.String;
throws-pattern: An exception type thrown by a method, such as java.lang.Exception;
example:
@Pointcut("execution(* com.toby.dao. *. * (.))")//any method matching any interface and class under the com.toby.dao package
@Pointcut("execution(public * com.toby.dao. *. * (.))")//match the public method for any interface and class under the com.toby.dao package
@Pointcut("execution(public * com.toby.dao. *. *()))")//Method matching public no method parameter for any interface and class under the com.toby.dao package
@Pointcut("execution(* com.toby.dao.*.*(java.lang.String,..))")//Match a String-type method with the first parameter of any interface and class under the com.toby.dao package
@Pointcut ("execution (* com.toby.dao. *. * (java.lang.String)")//Matches only one parameter for any interface and class under the com.toby.dao package with a method of type String
@Pointcut ("execution (* com.toby.dao. *. * (java.lang.String)")//Matches only one parameter for any interface and class under the com.toby.dao package with a method of type String
@Pointcut("execution(public * * (.))")//match any public method
@Pointcut("execution(* te*(.))")//Match any method that starts with te
@Pointcut("execution(* com.toby.dao.IndexDao. *(.))")//matches any method in the com.toby.dao.IndexDao interface
@Pointcut("execution(* com.toby.dao. *. * (.))")//match any method in the com.toby.dao package and its subpackages
For details on how to write this expression, refer to the official website: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples

Define an AopConfig configuration class:

package com.toby.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @desc: aop Configuration Class
 * @author: toby
 * @date: 2019/8/5 23:48
 */
@Configuration
/**
 * Note here that if the configuration setting proxyTargetClass=false or defaults to false, the JDK proxy is used, otherwise the CGLIB proxy is used
 * JDK The implementation of the proxy is based on the interface. The proxy class inherits Proxy and implements the interface.CGLIB inherits the proxy class to implement it.
 * So using a target keeps the target unchanged and matching the target object is not affected by this setting.
 * However, when using this option, CGLIB this can be proxied because it inherits the proxied object, which is also the target object, and JDK's this cannot be proxied.
 */
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan(basePackages="com.toby")
public class AopConfig {
}

Define a UserAspect:

package com.toby.aspect;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @desc: User facets, which must be handed over to the spring container
 * @author: toby
 * @date: 2019/8/5 22:56
 */
@Aspect
@Component
public class UserAspect {

    /**
     * For matching method execution join points. This is the primary pointcut designator to use when working with Spring AOP.
     * execution join points for matching method execution, minimum granularity method
     * Detailed usage reference:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples
     */
    @Pointcut("execution(* com.toby.service.UserService.*(..))")
    public void pointCutExecution(){}

    @Before("pointCutExecution()")
    public void before(){
        System.out.println("--------before--------");
    }

    @After("pointCutExecution()")
    public void after(){
        System.out.println("--------after--------");
    }
}

Define a startup test class, AopMain (the same below):

package com.toby;

import com.toby.config.AopConfig;
import com.toby.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @desc: aop Startup Class
 * @author: toby
 * @date: 2019/8/5 23:50
 */
public class AopMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
        UserService userService = context.getBean(UserService.class);
        userService.say("toby");
    }
}

The results are as follows:

Within: Used to match method execution within a specified type, within is more granular than execution and can only be implemented at the package and interface, class level.Execution can be precise to the return value of the method, the number of parameters, modifiers, parameter types, and so on.

Define a UserAspect:

package com.toby.aspect;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @desc: User facets, which must be handed over to the spring container
 * @author: toby
 * @date: 2019/8/5 22:56
 */
@Aspect
@Component
public class UserAspect {

    /**
     * Limits matching to join points within certain types (the execution of a method declared within a matching type when using Spring AOP).
     * Used to match method execution within a specified type, within is more granular than execution and can only be implemented at the package and interface, class level.Execution can be precise to the method's return value, number of parameters, modifiers, parameter types, etc.
     */
    @Pointcut("within(com.toby.service.impl.UserServiceImpl)")
    public void pointCutWithin(){}

    @Before("pointCutWithin()")
    public void before(){
        System.out.println("--------before--------");
    }

    @After("pointCutWithin()")
    public void after(){
        System.out.println("--------after--------");
    }
}

The results are as follows:

(3) this: the execution method used to match the current AOP proxy object type; note that the AOP proxy object type matches

Define a UserAspect:

package com.toby.aspect;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @desc: User facets, which must be handed over to the spring container
 * @author: toby
 * @date: 2019/8/5 22:56
 */
@Aspect
@Component
public class UserAspect {

    /**
     * Limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type.
     * Execution method used to match current AOP proxy object type; note that AOP proxy object type matches
     */
    @Pointcut("this(com.toby.service.impl.UserServiceImpl)")
    public void pointCutThis(){}

    @Before("pointCutThis()")
    public void before(){
        System.out.println("--------before--------");
    }

    @After("pointCutThis()")
    public void after(){
        System.out.println("--------after--------");
    }
}

@EnableAspectJAutoProxy(proxyTargetClass = true) is implemented using the CGLIB proxy, which inherits the proxy class, so this matches and runs as follows:

@ EnableAspectJAutoProxy, default proxyTargetClass = false, if interface-based then JDK proxy is used, so this does not match, run result:

(4) target: Execution method used to match the current target object type

Define a UserAspect:

package com.toby.aspect;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @desc: User facets, which must be handed over to the spring container
 * @author: toby
 * @date: 2019/8/5 22:56
 */
@Aspect
@Component
public class UserAspect {

    /**
     * Limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type.
     * Execution method used to match the current target object type
     */
    @Pointcut("target(com.toby.service.impl.UserServiceImpl)")
    public void pointCutTarget(){}

    @Before("pointCutTarget()")
    public void before(){
        System.out.println("--------before--------");
    }

    @After("pointCutTarget()")
    public void after(){
        System.out.println("--------after--------");
    }
}

The results are as follows:

_args: The parameter passed in to match the currently executing method is the execution method of the specified type

Define a UserAspect:

package com.toby.aspect;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @desc: User facets, which must be handed over to the spring container
 * @author: toby
 * @date: 2019/8/5 22:56
 */
@Aspect
@Component
public class UserAspect {

    /**
     * Limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types.
     * The parameter passed in to match the currently executing method is the execution method of the specified type
     */
    @Pointcut("args(java.lang.String)")
    public void pointCutArgs(){}

    @Before("pointCutArgs()")
    public void before(){
        System.out.println("--------before--------");
    }

    @After("pointCutArgs()")
    public void after(){
        System.out.println("--------after--------");
    }
}

Define a startup test class, AopMain:

package com.toby;

import com.toby.config.AopConfig;
import com.toby.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @desc: aop Startup Class
 * @author: toby
 * @date: 2019/8/5 23:50
 */
public class AopMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
        UserService userService = context.getBean(UserService.class);
        //Can't be enhanced
        userService.add();
        //Can be enhanced to match parameters String type
        userService.say("toby");
    }
}

The results are as follows:

_ @target: Does the matching target object type have a specified comment

Define the target object UserServiceImpl as follows:

package com.toby.service.impl;

import com.toby.anno.Toby;
import com.toby.service.UserService;
import org.springframework.stereotype.Service;

/**
 * @desc: user Business implementation class
 * @author: toby
 * @date: 2019/8/4 23:29
 */
@Service
@Toby
public class UserServiceImpl implements UserService {

    @Override
    public void add() {
        System.out.println("implement UserServiceImpl Of add Method");
    }

    @Override
    public String say(String name) {
        System.out.println("implement UserServiceImpl Of say Method args = " + name);
        return "hello " + name;
    }
}

Define a comment Log:

package com.toby.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @desc: Log Notes
 * @author: toby
 * @date: 2019/8/5 23:06
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}

Define a UserAspect:

package com.toby.aspect;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @desc: User facets, which must be handed over to the spring container
 * @author: toby
 * @date: 2019/8/5 22:56
 */
@Aspect
@Component
public class UserAspect {

    /**
     * Limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type.
     * Is there a specified comment for matching the target object type
     */
    @Pointcut("@target(com.toby.anno.Log)")
    public void pointCutTargetAnno(){}

    @Before("pointCutTargetAnno()")
    public void before(){
        System.out.println("--------before--------");
    }

    @After("pointCutTargetAnno()")
    public void after(){
        System.out.println("--------after--------");
    }
}

The results are as follows:

_ @args: There are specified comments on the type of matching method parameter (omitted examples)

_ @within: Used to match within the specified annotation type held, so the join point is also the method (omitted from the examples)

_ @annotation: Method used to match the current execution method with specified annotations (annotations work on methods)

Define the target object UserServiceImpl as follows:

package com.toby.service.impl;

import com.toby.anno.Log;
import com.toby.service.UserService;
import org.springframework.stereotype.Service;

/**
 * @desc: user Business implementation class
 * @author: toby
 * @date: 2019/8/4 23:29
 */
@Service
public class UserServiceImpl implements UserService {

    @Override
    public void add() {
        System.out.println("implement UserServiceImpl Of add Method");
    }

    @Override
    @Log
    public String say(String name) {
        System.out.println("implement UserServiceImpl Of say Method args = " + name);
        return "hello " + name;
    }
}

Define a UserAspect:

package com.toby.aspect;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @desc: User facets, which must be handed over to the spring container
 * @author: toby
 * @date: 2019/8/5 22:56
 */
@Aspect
@Component
public class UserAspect {

    /**
     * Limits matching to join points where the subject of the join point (the method being executed in Spring AOP) has the given annotation.
     * The method used to match the current execution method with the specified comment (the comment acts on the method);
     */
    @Pointcut("@annotation(com.toby.anno.Log)")
    public void pointCutAnno(){}

    @Before("pointCutAnno()")
    public void before(){
        System.out.println("--------before--------");
    }

    @After("pointCutAnno()")
    public void after(){
        System.out.println("--------after--------");
    }
}

Running found that userService.add(); //cannot be enhanced by userService.say("toby"); //can be enhanced because of the @Log annotation on the say method;

Summary: This chapter explains the core concepts and scenarios of Spring AOP, which can help us solve some cross-cutting issues in programming, such as logging, transaction management, performance monitoring, permission authentication, and so on.These issues can be separated from our business logic for decoupling purposes and code reuse.How to declare an Aspect, a Pointcut, and nine entry point expressions supported by Spring AOP.There are two proxy modes for Spring AOP, one is JDK and the other is CGLIB. If the configuration setting proxyTargetClass=false or default is false, the JDK proxy is used. Otherwise, the CGLIB proxy is used. Note that the JDK dynamic proxy must implement the interface, otherwise the CGLIB dynamic proxy will be moved along.(The reason will be analyzed in subsequent source parsing), the full code for the Spring series is in the code cloud: spring Series

Keywords: Java Spring JDK Programming

Added by jackiw on Fri, 16 Aug 2019 21:03:15 +0300