33

Spring系列(四):Spring AOP详解

 4 years ago
source link: https://www.tuicool.com/articles/UrQnA3V
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、AOP是什么

AOP(面向切面编程),可以说是一种编程思想,其中的Spring AOP和AspectJ都是现实了这种编程思想。相对OOP(面向过程编程)来说,提供了另外一种编程方式,对于OOP过程中产生的横切性问题,这些横切性与业务无关,可以通过预编译方式和运行期动态代理来实现。比如可以应用在:日志记录、性能监控、事务管理等。

二、AOP的基本概念

Aspect(切面):通常来说是一个类,里面定义了切点和通知,Spring AOP中可以用@AspectJ来标注这个类是切面;

Join point(连接点):可以理解成目标对象中的方法,该方法是要被增强的方法,也就是我们要作用的一个切入点;

Pointcut(切点):切点可以理解成连接点的集合;

Target object(目标对象):被代理的对象,也就是目标对象;

AOP proxy(代理对象):把被代理的对象织入了增强后的对象;

Weaving(织入):把增强也就是代理逻辑加入到目标对象上的过程;

Advice(通知):用于指定在特定连接点上的增强的位置;

① Before advice(前置通知):在目标方法被调用之前调用通知;

② After returning advice(返回通知):在目标方法成功执行之后调用通知;

③ After throwing advice(异常通知):在目标方法抛出异常后调用通知;

④ After (finally) advice(后置通知):在目标方法完成之后调用通知( 不论是否出现异常都会执行,finally中调用 );

⑤ Around advice(环绕通知):围绕连接点(目标方法)的通知。可以在方法调用前后执行自定义行为;

三、代理的实现方式

我们知道AOP可以通过预编译的方式和运行期动态代理来实现,那么代理的实现方式有哪些呢?

我们定义一个接口类:UserService

package com.toby.service;

/**
 * @desc: user 业务接口类
 * @author: toby
 * @date: 2019/8/4 23:28
 */
public interface UserService {
    /**
     * 添加
     */
    void add();
    
    /**
     * say hello
     * @param name
     * @return
     */
    String say(String name);
}

在定义一个实现类UserServiceImpl

package com.toby.service.impl;

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

/**
 * @desc: user业务的实现类
 * @author: toby
 * @date: 2019/8/4 23:29
 */
@Service
public class UserServiceImpl implements UserService {

    @Override
    public void add() {
        System.out.println("执行UserServiceImpl的add方法");
    }

    @Override
    public String say(String name) {
        System.out.println("执行UserServiceImpl的say方法 args = " + name);
        return "hello " + name;
    }
}

第一种:静态代理

定义一个静态代理类,需要实现UserService接口:

package com.toby.proxy;

import com.toby.service.UserService;

/**
 * @desc: 静态代理
 * @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("添加日志开始");
        userService.add();
        System.out.println("添加日志结束");
    }

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

第二种:Jdk动态代理

package com.toby.proxy;

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

/**
 * @desc: JDK动态代理 实现一个接口:InvocationHandler
 * JDK的动态代理机制只能代理实现了接口的类,而没有实现接口的类就不能实现JDK的动态代理
 * @author: toby
 * @date: 2019/8/4 23:34
 */
public class JdkDynamicProxy implements InvocationHandler {
    /**
     * 目标对象
     */
    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 {
        //写对应的增强代码
        System.out.println("Jdk日志记录开始");
        //调用真正的业务方法
        Object obj = method.invoke(this.targetObject,args);
        System.out.println("Jdk日志记录结束");
        return obj;
    }
}

第三种:Cglib动态代理

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动态代理 cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,
 * 并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理
 * @author: toby
 * @date: 2019/8/4 23:43
 */
public class CglibDynamicProxy implements MethodInterceptor {

    /**
     * 目标对象
     */
    private Object targetObject;

    /**
     * 创建Cglib动态代理
     * @param targetObject
     * @return
     */
    public Object createCglibDynamicProxy(final Object targetObject){
        this.targetObject = targetObject;
        //Cglib中的核心对象,该类用于生成代理对象
        Enhancer enhancer = new Enhancer();
        //指定委托类也就是目标对象为父类
        enhancer.setSuperclass(this.targetObject.getClass());
        //使用代理,需要一个对应的代理对象
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLIB日志记录开始");
        //委托类变成了父类。调用真正的服务提供者
        Object obj = methodProxy.invoke(this.targetObject,args);
        System.out.println("CGLIB日志记录结束");
        return obj;
    }
}

JDK代理的实现方式是基于接口实现,代理类继承Proxy,实现接口。而CGLIB继承被代理的类来实现;这就是为什么JDK动态代理需要实现接口的原因? J ava 是单继承

下面定义一个字节码生成器ByteCodeGenerator来一看究竟:

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: 字节码生成器
 * @author: toby
 * @date: 2019/8/5 0:05
 */
public class ByteCodeGenerator {
    /**
     * 根据目标对象生成字节码(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;
    }

    /**
     * 根据目标对象生成字节码(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) {
        /**
         * 测试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();
        }
        /**
         * 测试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();
        }
    }
}

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生成动态代理的字节码:

//
// 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();
    }
}

第四种:Javassist动态代理( 在动态字节码插桩详解

① 定义一个JavassistDynamicProxy实现Javassist动态代理:

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 {

    /**
     * 创建Javassist动态代理
     * @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)-> {
            //写对应的增强代码
            System.out.println("Javassist日志记录开始");
            //调用真正的业务方法
            Object obj = thisMethod.invoke(targetObject,args);
            System.out.println("Javassist日志记录结束");
            return obj;
        });
        return javassistProxy;
    }
}

② 定义一个JavassistBytecodeDynamicProxy实现Javassist动态代理:

package com.toby.proxy;

import javassist.*;

import java.lang.reflect.Field;

/**
 * @desc: javassist 字节码动态代理
 * @author: toby
 * @date: 2019/8/15 20:42
 */
public class JavassistBytecodeDynamicProxy {

    /**
     * 创建Javassist字节码动态代理
     * @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字节码日志记录开始\");\n" +
                "targetObject.add();\n" +
                "System.out.println(\"Javassist字节码日志记录结束\");\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;
    }

    /**
     * 创建Javassist字节码动态代理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字节码2日志记录开始\");");
        ctMethod.insertAfter("System.out.println(\"Javassist字节码2日志记录结束\");");
        Class<?> clazz = ctl.toClass();
        Object bytecodeProxy = clazz.newInstance();
        return bytecodeProxy;
    }
}

四、Spring AOP

Spring AOP提供两种编程风格,详细用法见Spring官网: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-aspectj-support

① @AspectJ support(利用aspectj的注解)

② Schema-based AOP support(基于xml aop:config命名空间)

启用@AspectJ支持

① 使用Java Configuration启用@AspectJ支持:要使用Java @Configuration启用@AspectJ支持,要添加@EnableAspectJAutoProxy注释:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

② 使用XML配置启用@AspectJ支持:要使用基于xml的配置启用@AspectJ支持,可以使用aop:aspectj-autoproxy元素

<aop:aspectj-autoproxy/>

声明一个Aspect

@Aspect
@Component
public class UserAspect {
}

声明一个Pointcut

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

Spring AOP支持的9种切入点表达式

① execution:execution用于匹配方法执行 join points连接点,最小粒度方法

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
这里问号表示当前项可以有也可以没有,其中各项的语义如下
modifiers-pattern:方法的可见性,如public,protected;
ret-type-pattern:方法的返回值类型,如int,void等;
declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
name-pattern:方法名类型,如buisinessService();
param-pattern:方法的参数类型,如java.lang.String;
throws-pattern:方法抛出的异常类型,如java.lang.Exception;
example:
@Pointcut("execution(* com.toby.dao.*.*(..))")//匹配com.toby.dao包下的任意接口和类的任意方法
@Pointcut("execution(public * com.toby.dao.*.*(..))")//匹配com.toby.dao包下的任意接口和类的public方法
@Pointcut("execution(public * com.toby.dao.*.*())")//匹配com.toby.dao包下的任意接口和类的public 无方法参数的方法
@Pointcut("execution(* com.toby.dao.*.*(java.lang.String, ..))")//匹配com.toby.dao包下的任意接口和类的第一个参数为String类型的方法
@Pointcut("execution(* com.toby.dao.*.*(java.lang.String))")//匹配com.toby.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(* com.toby.dao.*.*(java.lang.String))")//匹配com.toby.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(public * *(..))")//匹配任意的public方法
@Pointcut("execution(* te*(..))")//匹配任意的以te开头的方法
@Pointcut("execution(* com.toby.dao.IndexDao.*(..))")//匹配com.toby.dao.IndexDao接口中任意的方法
@Pointcut("execution(* com.toby.dao..*.*(..))")//匹配com.toby.dao包及其子包中任意的方法
关于这个表达式的详细写法,可以参考官网:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples

定义一个AopConfig配置类:

package com.toby.config;

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

/**
 * @desc: aop配置类
 * @author: toby
 * @date: 2019/8/5 23:48
 */
@Configuration
/**
 * 此处需要注意的是,如果配置设置proxyTargetClass=false,或默认为false,则是用JDK代理,否则使用的是CGLIB代理
 * JDK代理的实现方式是基于接口实现,代理类继承Proxy,实现接口。而CGLIB继承被代理的类来实现。
 * 所以使用target会保证目标不变,匹配目标对象不会受到这个设置的影响。
 * 但是使用this时,会根据该选项的设置,CGLIB this可以代理原因是继承了被代理的对象也就是目标对象,JDK的this就不能被代理了。
 */
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan(basePackages="com.toby")
public class AopConfig {
}

定义一个UserAspect:

package com.toby.aspect;

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

/**
 * @desc: 用户切面,该切面一定要交给spring容器管理
 * @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连接点,最小粒度方法
     * 详细用法参考: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--------");
    }
}

定义一个启动测试类AopMain( 下同 ):

package com.toby;

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

/**
 * @desc: aop启动类
 * @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");
    }
}

运行结果如下:

auqMJfE.png!web

② within:用于匹配指定类型内的方法执行, within与execution相比,粒度更大,仅能实现到包和接口、类级别。而execution可以精确到方法的返回值,参数个数、修饰符、参数类型等。

定义一个UserAspect:

package com.toby.aspect;

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

/**
 * @desc: 用户切面,该切面一定要交给spring容器管理
 * @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).
     * 用于匹配指定类型内的方法执行, within与execution相比,粒度更大,仅能实现到包和接口、类级别。而execution可以精确到方法的返回值,参数个数、修饰符、参数类型等
     */
    @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--------");
    }
}

运行结果如下:

77RFrqi.png!web

③ this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配 

定义一个UserAspect:

package com.toby.aspect;

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

/**
 * @desc: 用户切面,该切面一定要交给spring容器管理
 * @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.
     * 用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配
     */
    @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),则用CGLIB代理,而CGLIB继承被代理的类来实现,所以 this 能匹配到,运行结果:

77RFrqi.png!web

@EnableAspectJAutoProxy,默认proxyTargetClass = false,如果基于接口则用JDK代理,所以 this 匹配不到,运行结果:

NBNB7bz.png!web

④ target:用于匹配当前目标对象类型的执行方法

定义一个UserAspect:

package com.toby.aspect;

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

/**
 * @desc: 用户切面,该切面一定要交给spring容器管理
 * @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.
     * 用于匹配当前目标对象类型的执行方法
     */
    @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--------");
    }
}

运行结果如下:

vMRnyyu.png!web

⑤ args:用于匹配当前执行的方法传入的参数为指定类型的执行方法

定义一个UserAspect:

package com.toby.aspect;

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

/**
 * @desc: 用户切面,该切面一定要交给spring容器管理
 * @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.
     * 用于匹配当前执行的方法传入的参数为指定类型的执行方法
     */
    @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--------");
    }
}

定义一个启动测试类AopMain:

package com.toby;

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

/**
 * @desc: aop启动类
 * @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.add();
        //能被增强 匹配到了参数String类型
        userService.say("toby");
    }
}

运行结果如下:

rYVNjin.png!web

⑥ @target:匹配目标对象类型是否有指定的注解

定义目标对象UserServiceImpl如下:

package com.toby.service.impl;

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

/**
 * @desc: user业务的实现类
 * @author: toby
 * @date: 2019/8/4 23:29
 */
@Service
@Toby
public class UserServiceImpl implements UserService {

    @Override
    public void add() {
        System.out.println("执行UserServiceImpl的add方法");
    }

    @Override
    public String say(String name) {
        System.out.println("执行UserServiceImpl的say方法 args = " + name);
        return "hello " + name;
    }
}

定义一个注解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: 日志注解
 * @author: toby
 * @date: 2019/8/5 23:06
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}

定义一个UserAspect:

package com.toby.aspect;

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

/**
 * @desc: 用户切面,该切面一定要交给spring容器管理
 * @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.
     * 用于匹配目标对象类型是否有指定的注解
     */
    @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--------");
    }
}

运行结果如下:

FfaQnaY.png!web

@args: 匹配方法参数所属的类型上有指定的注解(例子略)

@within: 用于匹配所持有指定注解类型内的所以连接点也就是方法(例子略)

@annotation: 用于匹配当前执行方法持有指定注解的方法(注解作用在方法上面)

定义目标对象UserServiceImpl如下:

package com.toby.service.impl;

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

/**
 * @desc: user业务的实现类
 * @author: toby
 * @date: 2019/8/4 23:29
 */
@Service
public class UserServiceImpl implements UserService {

    @Override
    public void add() {
        System.out.println("执行UserServiceImpl的add方法");
    }

    @Override
    @Log
    public String say(String name) {
        System.out.println("执行UserServiceImpl的say方法 args = " + name);
        return "hello " + name;
    }
}

定义一个UserAspect:

package com.toby.aspect;

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

/**
 * @desc: 用户切面,该切面一定要交给spring容器管理
 * @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.
     * 用于匹配当前执行方法持有指定注解的方法(注解作用在方法上面);
     */
    @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--------");
    }
}

运行结果发现:userService.add();//不能被增强 userService.say("toby");//能被增强,因为say方法上有@Log注解;

总结:本章讲解了Spring AOP的核心概念和应用场景,Spring AOP可以帮我们解决编程过程中的一些横切性问题,比如我们要记录日志,事务管理,性能监控,权限认证等。使的这些问题能和我们业务逻辑分开,达到了解耦的目的,代码的重用性更高。如何声明一个Aspect,声明一个Pointcut以及Spring AOP支持的9种切入点表达式。Spring AOP的代理方式有2种,一个是JDK一个CGLIB,如果配置设置proxyTargetClass=false,或默认为false,则是用JDK代理,否则使用的是CGLIB代理,注意JDK动态代理必须实现接口,否则还是会走CGLIB动态代理。( 后续的源码解析会分析到原因 ),Spring系列完整代码在码云: spring系列


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK