欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring:从需求出发,读懂AOP如此easy -- 面向切面编程

程序员文章站 2022-05-08 09:22:30
...

闲言碎语:Spring中比较重要的两个点就是 IOC 和 AOP,前面有篇博客专门介绍了IOC的思想及使用,这篇文主要是通过实际的需求对 AOP 进行深入的研究,接地气的案例,我们一起来读懂 AOP。

AOP

AOP: Aspect Oriented Project --> 面向切面编程。

OOP: Object Oriented Project --> 面向对象编程。

1、AOP场景:

计算器添加日志记录:

Calculator 接口:

package com.xptx.inter;

public interface Calculator {
    int add(int x, int y);
    int sub(int x, int y);
    int mul(int x, int y);
    int div(int x, int y);
}

实现类:

package com.xptx.impl;

import com.xptx.inter.Calculator;

public class MyMathCalculator implements Calculator {
    public int add(int x, int y) {

        int result = x + y;
        return result;
    }

    public int sub(int x, int y) {
        int result = x - y;
        return result;
    }

    public int mul(int x, int y) {
        int result = x * y;
        return result;
    }

    public int div(int x, int y) {
        int result = x / y;
        return result;
    }
}

测试类:

import com.xptx.impl.MyMathCalculator;
import com.xptx.inter.Calculator;

public class Test {

    public static void main(String[] args) {
        Calculator calculator = new MyMathCalculator();

        int result = calculator.add(3, 5);
        System.out.println(result);

    }
}

Spring:从需求出发,读懂AOP如此easy -- 面向切面编程

1.1 添加日志记录:

在每一个方法中进行手动添加,直接编写在方法内部:

package com.xptx.impl;

import com.xptx.inter.Calculator;

public class MyMathCalculator implements Calculator {
    public int add(int x, int y) {
        System.out.println("[add] 方法开始了,它使用的参数是"+x + "," + y);
        int result = x + y;
        System.out.println("[add] 方法执行完成:" + "结果是:" + result);
        return result;
    }

    public int sub(int x, int y) {
        System.out.println("[sub] 方法开始了,它使用的参数是"+x + "," + y);
        int result = x - y;
        System.out.println("[sub] 方法执行完成:" + "结果是:" + result);
        return result;
    }

    public int mul(int x, int y) {
        System.out.println("[mul] 方法开始了,它使用的参数是"+x + "," + y);
        int result = x * y;
        System.out.println("[mul] 方法执行完成:" + "结果是:" + result);
        return result;
    }

    public int div(int x, int y) {
        System.out.println("[div] 方法开始了,它使用的参数是"+x + "," + y);
        int result = x / y;
        System.out.println("[div] 方法执行完成:" + "结果是:" + result);
        return result;
    }
}

Spring:从需求出发,读懂AOP如此easy -- 面向切面编程

通过上面的代码我们可以看到,每个方法由于方法名不同,对应的日志记录也是不同的,如果有成千上万个方法,我们就需要手动添加成千上万的语句,既麻烦还灵活性弱,代码量十分大,那有没有什么更好的方法呢?答案是肯定的。

  • 日志记录:系统的辅助功能。
  • 业务逻辑:核心功能。
  • 这种方法耦合性太强。

我们希望的是:

  • 业务逻辑: 核心功能。
  • 日志模块: 在核心功能运行期间,自己动态的加上

1.2 动态代理实现日志记录:

我们希望的是:

  • 业务逻辑: 核心功能。
  • 日志模块: 在核心功能运行期间,自己动态的加上

CalculatorProxy:

package com.xptx.proxy;

import com.xptx.inter.Calculator;

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

public class CalculatorProxy {

    public static Calculator getProxy(Calculator calculator) {
        InvocationHandler handler = new InvocationHandler() {
            /**
             *
             * @param proxy : 代理对象:给 jdk 使用,任何时候都不要动这个对象。
             * @param method: 当前将要执行的目标对象的方法。
             * @param args  : 这个方法调用时外界传入的值。
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    // 添加日志功能
                    System.out.println("["+method.getName()+"]方法开始执行,用的参数列表:"+ Arrays.asList(args));
                    // 目标方法执行后的返回值
                     result = method.invoke(calculator, args);
                    System.out.println("["+method.getName()+"]方法执行完成,计算结果是:"+ result);
                } catch (Exception e) {
                    System.out.println("["+method.getName()+"]方法出现异常,异常信息是:"+e.getMessage());
                } finally {
                    System.out.println("["+method.getName()+"]最终结束了");
                }
                return result;
            }
        };
        Object proxy = 	 Proxy.newProxyInstance(calculator.getClass().getClassLoader(),calculator.getClass().getInterfaces(),handler);
        return (Calculator) proxy;
    }
}

测试:

import com.xptx.impl.MyMathCalculator;
import com.xptx.inter.Calculator;
import com.xptx.proxy.CalculatorProxy;

public class Test {

    public static void main(String[] args) {
        // 被代理对象
        Calculator calculator = new MyMathCalculator();
        // 代理对象
        Calculator proxy = CalculatorProxy.getProxy(calculator);
        proxy.div(3, 1);
        proxy.add(5,6);
    }
}

Spring:从需求出发,读懂AOP如此easy -- 面向切面编程

从上述代码和结果可以看到,通过动态代理的方式实现了该类日志功能的动态添加,并且实现了日志和业务逻辑代码的分离

添加LogUtils实现:

LogUtils:

package com.xptx.utils;

        import java.lang.reflect.Method;
        import java.util.Arrays;

public class LogUtils {

    public static void logStart(Method method,Object[] args) {
        System.out.println("["+method.getName()+"]方法开始执行,用的参数列表:"+ Arrays.asList(args));
    }

    public static void logReturn(Method method,Object result){
        System.out.println("["+method.getName()+"]方法执行完成,计算结果是:"+ result);
    }

    public static void logException(Method method,Exception e) {
        System.out.println("["+method.getName()+"]方法出现异常,异常信息是:"+e.getCause());
    }

    public static void logEnd(Method method) {
        System.out.println("["+method.getName()+"]最终结束了");
    }
}


CalculatorProxy:

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    // 直接调用 LogUtils 中的静态方法
                    LogUtils.logStart(method,args);
                    // 目标方法执行后的返回值
                     result = method.invoke(calculator, args);
                    LogUtils.logReturn(method,result);
                } catch (Exception e) {
                   LogUtils.logException(method,e);
                } finally {
                    LogUtils.logEnd(method);

                }
                return result;
            }

Spring:从需求出发,读懂AOP如此easy -- 面向切面编程

可以使用动态代理将日志代码动态的在目标方法执行前后进行执行。

缺陷:

  • 写起来太过麻烦。
  • jdk 默认的动态代理,如果目标对象没有实现任何接口,是无法为它创建代理对象的,代理对象和被代理对象之间的唯一关联就是实现了相同的接口

1.3 Spring 弥补了动态代理的缺陷:

Spring 中的 AOP 底层就是动态代理:

  • 可以利用 Spring 一句代码都不写的去创建动态代理。
  • 实现简单,而且没有强制要求目标对象必须实现相同的接口

将某段代码(日志)动态的切入(不把代码写死到业务逻辑方法中)到指定方法(加减乘除)的指定位置(方法的开始、结束、异常。。)进行运行的编程方式(Spring 简化了面向切面编程)。

2、AOP 相关专业术语:

Spring:从需求出发,读懂AOP如此easy -- 面向切面编程

3、AOP 的简单配置:

3.1 使用步骤:

3.1.1 导包:

  		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.7.RELEASE</version>
            <scope>compile</scope>
        </dependency>

		/**
			Spring 支持面向切面编程的包是:
			基础版:
				spring-aspects
			加强版:
				加强版的面向切面编程,即使目标对象没有实现任何接口也能创建动态代理对象。
				com.springsource.net.sf.cglib
				com.springsource.org.aspectj.weaver
				com.springsource.org.aopalliance
			
		*/
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
		
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>

        <!--https://mvnrepository.com/artifact/net.sourceforge.cglib/
        com.springsource.net.sf.cglib -->
        <dependency>
            <groupId>net.sourceforge.cglib</groupId>
            <artifactId>com.springsource.net.sf.cglib</artifactId>
            <version>2.2.0</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>com.springsource.org.aspectj.weaver</artifactId>
            <version>1.6.8.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.aopalliance</groupId>
            <artifactId>com.springsource.org.aopalliance</artifactId>
            <version>1.0.0</version>
        </dependency>

3.1.2 配置

  • 将目标类和切面类加入到 ioc 容器中:
package com.xptx.impl;

import com.xptx.inter.Calculator;
import org.springframework.stereotype.Service;

/**
	将该目标类注册到 IOC 容器中
*/
@Service
public class MyMathCalculator implements Calculator {
    public int add(int x, int y) {
        int result = x + y;
        return result;
    }

    public int sub(int x, int y) {
        int result = x - y;
        return result;
    }

    public int mul(int x, int y) {
        int result = x * y;
        return result;
    }

    public int div(int x, int y) {
        int result = x / y;
        return result;
    }
}



package com.xptx.utils;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 *
 *  切面类: 如何将这个类中的所有方法(通知方法)动态的在目标方法的各个位置切入
 *
 */

// 将切面类注册到 IOC 容器中
@Component
@Aspect
public class LogUtils {

    public static void logStart(Method method, Object[] args) {
        System.out.println("[" + method.getName() + "]方法开始执行,用的参数列表:" + Arrays.asList(args));
    }

    public static void logReturn(Method method, Object result) {
        System.out.println("[" + method.getName() + "]方法执行完成,计算结果是:" + result);
    }

    public static void logException(Method method, Exception e) {
        System.out.println("[" + method.getName() + "]方法出现异常,异常信息是:" + e.getCause());
    }

    public static void logEnd(Method method) {
        System.out.println("[" + method.getName() + "]最终结束了");
    }
}

<!-- 开启注解自动扫描某个包下的所有类,注册到 Spring 的 IOC 容器中 -->

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
">
	
    <context:component-scan base-package="com.xptx"/>

</beans>
  • 告诉 Spring 哪个是切面类:
package com.xptx.utils;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 *
 *  切面类: 如何将这个类中的所有方法(通知方法)动态的在目标方法的各个位置切入
 *
 */

// 将切面类注册到 IOC 容器中
@Component
// 告诉 Spring 哪个是切面类
@Aspect
public class LogUtils {

    public static void logStart(Method method, Object[] args) {
        System.out.println("[" + method.getName() + "]方法开始执行,用的参数列表:" + Arrays.asList(args));
    }

    public static void logReturn(Method method, Object result) {
        System.out.println("[" + method.getName() + "]方法执行完成,计算结果是:" + result);
    }

    public static void logException(Method method, Exception e) {
        System.out.println("[" + method.getName() + "]方法出现异常,异常信息是:" + e.getCause());
    }

    public static void logEnd(Method method) {
        System.out.println("[" + method.getName() + "]最终结束了");
    }
}

  • 告诉Spring,切面类中的每一个方法,何时何地运行:
package com.xptx.utils;

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

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 *
 *  切面类: 如何将这个类中的所有方法(通知方法)动态的在目标方法的各个位置切入
 *
 */

// 将切面类注册到 IOC 容器中
@Component
// 告诉Spring 这是个切面类
@Aspect
public class LogUtils {
    /**
     *      告诉 Spring 每个方法什么时候执行
     *
     *      通知注解
     *
     *      @Before : 在目标方法之前运行                         前置通知
     *      @After  : 在目标方法之后执行                         后置通知
     *      @AfterReturning : 在目标方法正常返回之后运行          返回通知
     *      @AfterThrowing  : 在目标方法抛出异常之后执行          异常通知
     *      @Around : 环绕                                      环绕通知
     */


    /**
     *  想在目标方法之前运行
     */
    @Before("execution(public * com.xptx.impl.MyMathCalculator.*(int,int))")
    public static void logStart() {
        System.out.println("[xxx]方法开始执行,用的参数列表");
    }


    /**
     * 在目标方法正常返回之后运行
     */
    @AfterReturning("execution(public * com.xptx.impl.MyMathCalculator.*(int,int))")
    public static void logReturn() {
        System.out.println("[xxx]方法执行完成,计算结果是:");
    }

    /**
     *  在目标方法抛出异常之后执行
     */
    @AfterThrowing("execution(public * com.xptx.impl.MyMathCalculator.*(int,int))")
    public static void logException() {
        System.out.println("[xxx]方法出现异常,异常信息是:");
    }

    /**
     * 在目标方法之后执行
     */
    @After("execution(public * com.xptx.impl.MyMathCalculator.*(int,int))")
    public static void logEnd() {
        System.out.println("[xxx]最终结束了");
    }
}

  • 开启基于注解的 AOP 功能:

    xmlns:aop="http://www.springframework.org/schema/aop"
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    

3.1.3 测试:

import com.xptx.impl.MyMathCalculator;
import com.xptx.inter.Calculator;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class Test {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Calculator bean = (Calculator) context.getBean("myMathCalculator");
        bean.add(2,3);
    }
}

Spring:从需求出发,读懂AOP如此easy -- 面向切面编程

3.2 AOP 中的细节问题:

3.2.1 ioc 容器中保存的是组件的代理对象:

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        /**
         *  从 IOC 容器中拿到目标对象。
         *  注意: 如果使用类型,一定要用它的接口类型
         */
        Calculator bean = context.getBean(Calculator.class);
        System.out.println(bean.getClass());
        bean.add(2,3);
    }

Spring:从需求出发,读懂AOP如此easy -- 面向切面编程

cglib 为没有接口的组件创建代理对象:

package com.xptx.impl;

import com.xptx.inter.Calculator;
import org.springframework.stereotype.Service;

/**
*  该类没有实现任何的接口
*/
@Service
public class MyMathCalculator  {
    public int add(int x, int y) {
        int result = x + y;
        return result;
    }

    public int sub(int x, int y) {
        int result = x - y;
        return result;
    }

    public int mul(int x, int y) {
        int result = x * y;
        return result;
    }

    public int div(int x, int y) {
        int result = x / y;
        return result;
    }
}

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 没有接口就是本类对象
        MyMathCalculator bean = context.getBean(MyMathCalculator.class);
        bean.add(3,4);
        System.out.println(bean.getClass());
    }

Spring:从需求出发,读懂AOP如此easy -- 面向切面编程

3.2.2 切入点表达式:

切入表达式的写法:

固定格式: execution(访问权限符 返回值类型 方法全类名(参数表))

通配符:

  • *:

    • 匹配一个或多个字符

      // 匹配多个字符
      public * com.xptx.impl.MyMath*.add(int,int))
      // 匹配一个字符
      public * com.xptx.impl.MyMath*r.add(int,int))
      
    • 匹配任意一个参数:

      public * com.xptx.impl.MyMathCalculator.*(int,*))
      
    • 权限位置不能用 *: public(可选)

  • … :

    • 匹配任意多个参数,匹配任意类型参数:

      execution(public * com.xptx.impl.MyMathCalculator.*(..))"
      
    • 匹配任意多层路径:

      execution(public * com.xptx..MyMathCalculator.*(..))"
      

3.3.3 通知方法的执行顺序:

  • 正常执行: @Before(前置通知) --> @After(后置通知) -->@AfterReturning(正常返回)
  • 异常执行: @Before(前置通知) --> @After(后置通知) -->@AfterThrowing(异常返回)

3.3.4 JoinPoint 获取目标方法的信息:

如何在通知方法运行的时候,拿到目标方法的详细信息:

  • 在通知方法的参数列表上写一个参数:
    • JoinPoint joinPoint : 封装了当前目标方法的详细信息。
    @Before("execution(public * com.xptx.impl.MyMathCalculator.*(..))")
    public static void logStart(JoinPoint joinPoint) {
        // 获取参数列表
        Object[] args = joinPoint.getArgs();
        // 得到方法的信息
        String name = joinPoint.getSignature().getName();
        System.out.println("["+name+"]方法开始执行,用的参数列表"+Arrays.asList(args));
    }

3.3.5 得到异常信息和返回结果:

得到异常信息和返回结果:

  • 告诉Spring 这个方法需要返回结果:

    • returning = “result”
  • 告诉Spring 哪个参数用来接收异常:

    • throwing=“e” – 告诉Spring 哪个参数用来接收异常。
    @AfterReturning(value = "execution(public * com.xptx.impl.MyMathCalculator.*(..))",returning = "result")
    public static void logReturn(JoinPoint joinPoint,Object result) {
        String name = joinPoint.getSignature().getName();
        System.out.println("["+name+"]方法执行完成,计算结果是:"+result);
    }
    /**
     *  在目标方法抛出异常之后执行
     */
    @AfterThrowing(value = "execution(public * com.xptx.impl.MyMathCalculator.*(int,int))",throwing = "e")
    public static void logException(JoinPoint joinPoint,Exception e) {
        System.out.println("["+joinPoint.getSignature().getName()+"]方法出现异常,异常信息是:"+e);
    }

3.3.6 Spring 对通知方法的约束:

方法的参数列表一定不能乱写,通知方法是 Spring 利用反射调用的,每次方法调用都得确定这个方法的参数表的值,参数表上的每一个参数,Spring 都得知道。

3.3.7 抽取可重用的切点表达式:

  • 随便声明一个没有实现的 返回 void 的空方法。
  • 给方法标注 @Pointcut 注解。
    // 可重用的切入点表达式,这样用起来修改更加方便
	@Pointcut("execution(public * com.xptx.impl.MyMathCalculator.*(int,int))")
    public void myPoint(){};

    /**
     *  想在目标方法之前运行
     */
	// 直接重用切入点表达式即可。
    @Before("myPoint()")
    public static void logStart(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println("["+name+"]方法开始执行,用的参数列表"+Arrays.asList(args));
    }

    /**
     * 在目标方法正常返回之后运行
     */
    @AfterReturning(value = "myPoint()",returning = "result")
    public static void logReturn(JoinPoint joinPoint,Object result) {
        String name = joinPoint.getSignature().getName();
        System.out.println("["+name+"]方法执行完成,计算结果是:"+result);
    }

    /**
     *  在目标方法抛出异常之后执行
     */
    @AfterThrowing(value = "myPoint()",throwing = "e")
    public static void logException(JoinPoint joinPoint,Exception e) {
        System.out.println("["+joinPoint.getSignature().getName()+"]方法出现异常,异常信息是:"+e);
    }

    /**
     * 在目标方法之后执行
     */
    @After("myPoint()")
    public static void logEnd(JoinPoint joinPoint) {
        System.out.println("["+joinPoint.getSignature().getName()+"]最终结束了");
    }

3.3.8 环绕通知:

@Around 环绕通知: Spring 中最强大的通知。

try{
    // 前置通知
    method.invoke(obj,args);
    // 返回通知
} catch(Exception e) {
    // 异常通知
} finally{
  	// 后置通知   
}

这四个通知合起来就是 环绕通知。

    @Pointcut("execution(public * com.xptx.impl.MyMathCalculator.*(int,int))")
    public void myPoint(){};

    /**
     *   环绕通知,从代码中可以看到环绕通知中就包括了其它 4 个通知。
     */
    @Around("myPoint()")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        // 获取方法名
        String name = pjp.getSignature().getName();
        Object proceed = null;
        try{
            System.out.println("环绕前置通知:"+ name + "方法开始执行。");
            // 就是利用 反射调用 目标方法即可,实际上就是 method.invoke(obj,args);
            proceed = pjp.proceed(args);
            System.out.println("环绕返回通知:"+ name + "方法返回执行完成,执行结果是:"+proceed);
        } catch (Exception e) {
            System.out.println("环绕异常通知.异常信息:"+ e.getCause());
        } finally {
            System.out.println("环绕后置通知:"+name +"执行结束");
        }
        return proceed;
    }

环绕通知:优于普通通知执行。

  • 环绕只是影响当前切面。

3.3.9 多切面运行顺序:

LogUtilsAspect

package com.xptx.utils;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 *
 *  切面类: 如何将这个类中的所有方法(通知方法)动态的在目标方法的各个位置切入
 *
 */

// 将切面类注册到 IOC 容器中
@Component
// 告诉Spring 这是个切面类
@Aspect
public class LogUtils {
    @Pointcut("execution(public * com.xptx.impl.MyMathCalculator.*(int,int))")
    public void myPoint(){};

    /**
     *  想在目标方法之前运行
     */
    @Before("myPoint()")
    public static void logStart(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println("LogUtils["+name+"]方法开始执行,用的参数列表"+Arrays.asList(args));
    }

    /**
     * 在目标方法正常返回之后运行
     */
    @AfterReturning(value = "myPoint()",returning = "result")
    public static void logReturn(JoinPoint joinPoint,Object result) {
        String name = joinPoint.getSignature().getName();
        System.out.println("LogUtils["+name+"]方法执行完成,计算结果是:"+result);
    }

    /**
     *  在目标方法抛出异常之后执行
     */
    @AfterThrowing(value = "myPoint()",throwing = "e")
    public static void logException(JoinPoint joinPoint,Exception e) {
        System.out.println("LogUtils["+joinPoint.getSignature().getName()+"]方法出现异常,异常信息是:"+e);
    }

    /**
     * 在目标方法之后执行
     */
    @After("myPoint()")
    public static void logEnd(JoinPoint joinPoint) {
        System.out.println("LogUtils["+joinPoint.getSignature().getName()+"]最终结束了");
    }

    /**
     *   环绕通知
     */
    //@Around("myPoint()")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        // 获取方法名
        String name = pjp.getSignature().getName();
        Object proceed = null;
        try{
            System.out.println("环绕前置通知:"+ name + "方法开始执行。");
            // 就是利用 反射调用 目标方法即可,实际上就是 method.invoke(obj,args);
            proceed = pjp.proceed(args);
            System.out.println("环绕返回通知:"+ name + "方法返回执行完成,执行结果是:"+proceed);
        } catch (Exception e) {
            System.out.println("环绕异常通知.异常信息:"+ e.getCause());
        } finally {
            System.out.println("环绕后置通知:"+name +"执行结束");
        }
        return proceed;
    }


}

ValidateAspect

package com.xptx.utils;

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

import java.util.Arrays;

@Component
@Aspect
public class ValidateAspect {

    @Before("com.xptx.utils.LogUtils.myPoint()")
    public static void logStart(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        String name = joinPoint.getSignature().getName();
        System.out.println("Va["+name+"]方法开始执行,用的参数列表"+ Arrays.asList(args));
    }


    @AfterReturning(value = "com.xptx.utils.LogUtils.myPoint()",returning = "result")
    public static void logReturn(JoinPoint joinPoint,Object result) {
        String name = joinPoint.getSignature().getName();
        System.out.println("Va["+name+"]方法执行完成,计算结果是:"+result);
    }


    @AfterThrowing(value = "com.xptx.utils.LogUtils.myPoint()",throwing = "e")
    public static void logException(JoinPoint joinPoint,Exception e) {
        System.out.println("Va["+joinPoint.getSignature().getName()+"]方法出现异常,异常信息是:"+e);
    }


    @After("com.xptx.utils.LogUtils.myPoint()")
    public static void logEnd(JoinPoint joinPoint) {
        System.out.println("Va["+joinPoint.getSignature().getName()+"]最终结束了");
    }

}

Spring:从需求出发,读懂AOP如此easy -- 面向切面编程

Spring:从需求出发,读懂AOP如此easy -- 面向切面编程

切面类会按照类名的字典序进行排列,先进后出的顺序。

如何改变顺序:

Spring:从需求出发,读懂AOP如此easy -- 面向切面编程

Spring:从需求出发,读懂AOP如此easy -- 面向切面编程

4、AOP 的使用场景:

  1. AOP 加日志保存到数据库。
  2. AOP 做权限验证(前置通知)。
  3. AOP 做安全检查。
  4. AOP 做事务控制。

5、基于配置的AOP:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
">


    <context:component-scan base-package="com.xptx">
    </context:component-scan>

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <!-- 基于配置的 AOP   -->

    <bean id="myMathCalculator" class="com.xptx.impl.MyMathCalculator" />
    <bean id="validateAspect" class="com.xptx.utils.ValidateAspect"/>
    <bean id="logUtils" class="com.xptx.utils.LogUtils"/>


    <!-- 需要 AOP 命名空间 -->

    <aop:config>
        <aop:pointcut id="myPoint" expression="execution(public * com.xptx.impl.MyMathCalculator.*(int,int))"/>
        <!-- 指定切面 order 是优先级 -->
        <aop:aspect ref="logUtils" order="1">
            <!--  当前切面能用的 -->
            <aop:pointcut id="myPoint" expression="execution(public * com.xptx.impl.MyMathCalculator.*(int,int))"/>
            <aop:before method="logStart" pointcut-ref="myPoint"></aop:before>
            <aop:after-returning method="logReturn" pointcut-ref="myPoint" returning="result"/>
            <aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="e"/>
            <aop:after method="logEnd" pointcut-ref="myPoint"/>
            <aop:around method="myAround" pointcut-ref="myPoint"/>
        </aop:aspect>
        <aop:aspect ref="validateAspect">
            <aop:before method="logStart" pointcut-ref="myPoint"/>
            <aop:after-returning method="logReturn" pointcut-ref="myPoint" returning="result"/>
            <aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="e"/>
            <aop:after method="logEnd" pointcut-ref="myPoint"/>
        </aop:aspect>
    </aop:config>
</beans>

Spring:从需求出发,读懂AOP如此easy -- 面向切面编程

重要的用配置。