AOP

发布时间 2023-08-16 23:23:38作者: 蝶凌天

AOP

一、什么是AOP?

Aop的专业术语(来源百度):

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

总结:

AOP是面向切面编程的语言,使自己的业务代码和非业务代码之间进行隔离,并且在不改变业务代码的前提下,可以增加新的非业务代码。

二、为什么使用AOP?

思考:

​ 如果我们在做用户登录和用户登出时,需要把这个记录添加到记录表时,这时,我们该如何处理。如果仅仅只有少量的记录我们或许可以去解决,可是当数量一旦多的话就会很麻烦。这个时候我们可以使用面向切面编程解决。

思维导图:
image


三、AOP的应用场景

1、Authentication 权限
2、Caching 缓存
3、Context passing 内容传递
4、Error handling 错误处理
5、Lazy loading 懒加载
6、Debugging 调试
7、logging,tracing,profiling and monitoring 记录跟踪 优化 校准
8、Performance optimization 性能优化
9、Persistence 持久化
10、Resource pooling 资源池
11、Synchronization 同步
12、Transactions 事务
13、Logging 日志

四、AOP的结构

其实AOP编程很简单,在AOP中程序员只需要关注三个部分:
1、在哪里切入(权限校验等业务操作在哪些业务代码中执行);
2、什么时候切入(切入的时机是在业务代码执行前还是执行后);
3、切入代码后做什么事(权限校验。日志记录等)
image
Asperct:切面
PointCut:切点 :路径表达式 (2)注解
Advice:处理的时机

五、如何使用AOP

以记录日志为例:

public class MathServiceImpl implements MathService{
    @Override
    public double add(double a, double b) {
        double result=a+b;
        System.out.println("这是记录的加法运算"+result);
        return result;
    }

    @Override
    public double sub(double a, double b) {
        double result=a-b;
        System.out.println("这是记录的减法运算"+result);
        return result;
    }

    @Override
    public double mul(double a, double b) {
        double result=a*b;
        System.out.println("这是记录的乘法运算"+result);
        return result;
    }

    @Override
    public double div(double a, double b) {
        double result=a/b;
        System.out.println("这是记录的除法运算"+result);
        return result;
    }
}

发现:在每个操作后,都要记录日志,如果后期日志内容发生改变。需要在每个操作后都进行修改,不利于代码的维护。

5.1 使用AOP解决

(1)引入相关依赖(注意:最好使用JDK8,否则在运行时,可能会报错)

<!--  Spring 依赖  -->
   <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-webmvc</artifactId>
     <version>5.2.15.RELEASE</version>
   </dependency>
   <!--引入SpringAop 依赖-->
   <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-aspects</artifactId>
     <version>5.2.15.RELEASE</version>
   </dependency>
 </dependencies>

(2)创建一个切面类

@Aspect // 标记该类为切面类
@Component // 将该类对象的创建交予spring容器(Brean)来管理
public class MyAspect {
    // Pointcut 切点  将要切点的路径写入当前注解中
   @Pointcut(value = "execution(public double com.deom.dome5.MathServiceImpl.add(double,double))")
    public void mypointcut(){

    }
    @After(value = "mypointcut()")
    public void a(){
        System.out.println("代码执行后");
    }
}

(3)创建一个spring配置文件

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
       <!--包扫描-->
   <context:component-scan base-package="com.deom.dome5"/>
        <!--开启Aop切面注解驱动-->
    <aop:aspectj-autoproxy/>
</beans>

(4)测试

public class Text5 {
    public static void main(String[] args) {
        // 加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");
        MathService mathService = (MathService) context.getBean("mathServiceImpl");
        mathService.add(20,10);
    }
}

运行结果:
image

5.2 使用通配符,统配类的路径

@Aspect // 标记该类为切面类
@Component // 将该类对象的创建交予spring容器(Brean)来管理
public class MyAspect {
/*
*  通配符: *
*   第一个 * : 表示任意的访问权限修饰符、任意返回类型
*   第二个 * : 表示该包下所有的类。
*   第三个* : 类下的所有方法
*   三个... 可变参数
*   这里的两个 .. : 任意参数
*
*  个人建议:包级别,不推荐使用通配符
* */
    @Pointcut(value = "execution(* com.deom.dome5.*.*(..))")
    public void mypointcut1(){

    }

//    // Pointcut 切点  将要切点的路径写入当前注解中
//   @Pointcut(value = "execution(public double com.deom.dome5.MathServiceImpl.add(double,double))")
//    public void mypointcut(){
//
//    }
    @After(value = "mypointcut1()")
    public void a(){
        System.out.println("代码执行后");
    }
}

5.3 使用注解实现AOP

(1)注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "";
}

(2)ServieImpl

@Service
public class MathServiceImpl implements MathService {
    // 这里使用了自定义注解
    @MyAnnotation
    @Override
    public double add(double a, double b) {
         // int i=10/0  手动开启或关闭异常报错
        double result=a+b;
        System.out.println("这是记录的加法运算"+result);
        return result;
    }

    @Override
    public double sub(double a, double b) {
        double result=a-b;
        System.out.println("这是记录的减法运算"+result);
        return result;
    }

    @Override
    public double mul(double a, double b) {
        double result=a*b;
        System.out.println("这是记录的乘法运算"+result);
        return result;
    }

    @Override
    public double div(double a, double b) {
        double result=a/b;
        System.out.println("这是记录的除法运算"+result);
        return result;
    }
}

(3)修改切面类

@Aspect // 标记该类为切面类
@Component // 将该类对象的创建交予spring容器(Brean)来管理
public class MyAspect {
/*
*  通配符: *
*   第一个 * : 表示任意的访问权限修饰符、任意返回类型
*   第二个 * : 表示该包下所有的类。
*   第三个* : 类下的所有方法
*   三个... 可变参数
*   这里的两个 .. : 任意参数
*
*  个人建议:包级别,不推荐使用通配符
* */
//    @Pointcut(value = "execution(* com.deom.dome5.*.*(..))")
//    public void mypointcut1(){
//
//    }

//    // Pointcut 切点  将要切点的路径写入当前注解中
//   @Pointcut(value = "execution(public double com.deom.dome5.MathServiceImpl.add(double,double))")
//    public void mypointcut(){
//
//    }

// @annotation 调用注解的方法,括号里面写自定义注解的路径
    @Pointcut(value = "@annotation(com.deom.dome5.MyAnnotation)")
    public void mypointcut2(){

    }

    @After(value = "mypointcut2()")
    public void a(){
        System.out.println("代码执行后");
    }
}

(4)测试:

public class Text5 {
    public static void main(String[] args) {
        // 加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");
        MathService mathService = (MathService) context.getBean("mathServiceImpl");
        mathService.add(20,10);
    }
}

运行结果:
image

5.4 aop切面通知的类型

(1)四种简单的处理方法
1、Before(前置处理)
2、After (后置处理)
3、AfterReturning (后置返回通知)
4、AfterThrowing (异常处理)

@Aspect // 标记该类为切面类
@Component // 将该类对象的创建交予spring容器(Brean)来管理
public class MyAspect {
/*
*  通配符: *
*   第一个 * : 表示任意的访问权限修饰符、任意返回类型
*   第二个 * : 表示该包下所有的类。
*   第三个* : 类下的所有方法
*   三个... 可变参数
*   这里的两个 .. : 任意参数
*
*  个人建议:包级别,不推荐使用通配符
* */
//    @Pointcut(value = "execution(* com.deom.dome5.*.*(..))")
//    public void mypointcut1(){
//
//    }

//    // Pointcut 切点  将要切点的路径写入当前注解中
//   @Pointcut(value = "execution(public double com.deom.dome5.MathServiceImpl.add(double,double))")
//    public void mypointcut(){
//
//    }

    // @annotation 调用注解的方法,括号里面写自定义注解的路径
    @Pointcut(value = "@annotation(com.deom.dome5.MyAnnotation)")
    public void mypointcut2(){

    }
    // 后置处理 执行完代码后不需要经过返回值Return
    @After(value = "mypointcut2()")
    public void a(){
        System.out.println("Pointcut~~代码执行后,后置通知~~~Pointcut");
    }
    // 前置通知 方法执行前,执行切面的内容  前置通知
    @Before(value = "mypointcut2()")
    public void b(){
        System.out.println("Before~~~~~~~~~~前置通知,方法执行前,执行切面内容~~~~~~~~~~~~~Before");
    }
    // 后置返回通知,触碰到return,如果方法出现异常;这种通知不会被执行
    @AfterReturning(value ="mypointcut2()",returning = "r")// returnning:把执行方法的结果赋给该变量参数r
    public void  c(Object r){ // 参数名必须和returnning的名称保持一致
        System.out.println("AfterReturning~~~后置返回通知,触碰到return~~~AfterReturning");
    }
    // 异常通知:当被切入的方法出现异常时 ,才会执行
    @AfterThrowing(value = "mypointcut2()")
    public void d(){
        System.out.println("AfterThrowing~~异常通知,出现异常时出现~~~AfterThrowing");
    }
}

运行结果:
无异常:
image

有异常:
image

(2)环绕通知

@Aspect // 标记该类为切面类
@Component // 将该类对象的创建交予spring容器(Brean)来管理
public class MyAspect {
/*
*  通配符: *
*   第一个 * : 表示任意的访问权限修饰符、任意返回类型
*   第二个 * : 表示该包下所有的类。
*   第三个* : 类下的所有方法
*   三个... 可变参数
*   这里的两个 .. : 任意参数
*
*  个人建议:包级别,不推荐使用通配符
* */
//    @Pointcut(value = "execution(* com.deom.dome5.*.*(..))")
//    public void mypointcut1(){
//
//    }

//    // Pointcut 切点  将要切点的路径写入当前注解中
//   @Pointcut(value = "execution(public double com.deom.dome5.MathServiceImpl.add(double,double))")
//    public void mypointcut(){
//
//    }

    // @annotation 调用注解的方法,括号里面写自定义注解的路径
    @Pointcut(value = "@annotation(com.deom.dome5.MyAnnotation)")
    public void mypointcut2(){

    }
//    // 后置处理 执行完代码后不需要经过返回值Return
//    @After(value = "mypointcut2()")
//    public void a(){
//        System.out.println("After~~代码执行后,后置通知~~~After");
//    }
//    // 前置通知 方法执行前,执行切面的内容  前置通知
//    @Before(value = "mypointcut2()")
//    public void b(){
//        System.out.println("Before~~~~~~~~~~前置通知,方法执行前,执行切面内容~~~~~~~~~~~~~Before");
//    }
//    // 后置返回通知,触碰到return,如果方法出现异常;这种通知不会被执行
//    @AfterReturning(value ="mypointcut2()",returning = "r")// returnning:把执行方法的结果赋给该变量参数r
//    public void  c(Object r){ // 参数名必须和returnning的名称保持一致
//        System.out.println("AfterReturning~~~后置返回通知,触碰到return~~~AfterReturning");
//    }
//    // 异常通知:当被切入的方法出现异常时 ,才会执行
//    @AfterThrowing(value = "mypointcut2()")
//    public void d(){
//        System.out.println("AfterThrowing~~异常通知,出现异常时出现~~~AfterThrowing");
//    }

   // 环绕通知
    @Around(value = "mypointcut2()")
    // ProceedingJoinPoint:继承了 JoinPoint,是在JoinPoint的基础上暴露出 proceed 这个方法。
    // Proceedingjoinpoint 仅支持环绕通知@Around,而其他的几种切面只需要用到JoinPoint,这也是环绕通知和前置、后置通知方法的一个最大区别。这跟切面类型有关)
    // 环绕通知 = 前置 + 目标方法执行 + 后置通知,proceed方法就是用于启动目标方法的执行。暴露出这个方法,就能支持 aop:around 这种切面
    public Object e(ProceedingJoinPoint joinPoint){ // joinPoint:连接点  执行的方法对象,使用proceed方法启动目标方法的执行
        System.out.println("Around~~前置通知(Before)~~~Around");

        try {
            Object proceed = joinPoint.proceed();// 执行连接点
            System.out.println("Around~~后置通知(After)~~~Around");
            return proceed;
        } catch (Throwable e) {
            // printStackTrace:  在命令行打印异常信息在程序中出错的位置及原因
            e.printStackTrace();
            System.out.println("Around~~异常通知(AfterThrowing)~~~Around");
        } finally {
            System.out.println("都会执行");
        }
        return 0.0;
    }

运行结果:
无异常:
image
有异常:
image