# Aop 简介

  • AOP(面向切面编程)是一种编程范式和一种在软件系统中实现关注点分离的技术。
  • AOP 通过在特定的切入点处自动执行预定义的操作,将这些功能与核心业务逻辑进行解耦。
  • 它允许您将与核心业务逻辑无关的功能(例如日志记录、事务管理、性能监控等)从源代码中分离出来,并以模块化的方式应用于整个应用程序。
  • 在 AOP 中,切面是一种模块化单元,它包含了与特定横切关注点相关的通用功能。
  • 切面由切点(定义将在何处应用通用功能)和建议(定义在切点处要执行的操作)组成。
  • 切点是一个表达式,描述了在应用程序的执行过程中,具体哪些方法或函数将被拦截以应用通用功能。
  • 建议定义了在切点处执行的特定操作,例如在方法调用之前或之后执行代码,甚至替换原始的方法调用。
  • 在 Java 中,AOP 常常使用 AspectJ 库实现。
  • Spring 框架也提供了对 AOP 的支持,并且使用 AspectJ 作为默认的 AOP 框架。
  • Spring AOP 使得在应用程序中使用 AOP 更加简单和直观,可以通过使用注解或 XML 配置定义切面和切点,实现通用功能的应用。

# Aop 优点

  • 关注点分离:
    • AOP 使得您可以将与核心业务逻辑无关的功能从代码中分离出来,使得代码更加干净和易于维护。
  • 代码重用:
    • AOP 允许您将通用功能定义在一个地方,并在整个应用程序中多次重用。
  • 横向关注点:
    • AOP 以横向的方式独立于代码的纵向层次结构,因此它可以跨越多个层次结构应用于不同的模块和类。
  • 核心逻辑影响较小:
    • 引入 AOP 并不会对核心业务逻辑产生太大的影响,只需要在切入点处应用即可。

# 什么是 AOP

  1. AOPAspect Oriented Programming (面向切面编程), OOP 是面向对象编程, AOP 是在 OOP
    基础之上的一种更高级的设计思想。
  2. OOPAOP 之间也存在一些区别, OOP 侧重于对象的提取和封, AOP
    侧重于组件方面,组件可以理解成封装了通用功能的组件,组件可以通过配置方式,灵活地切入到某一批目标对象方法上。
  3. AOP 用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等。

# 为什么使用 AOP

  • Java 是一个面向对象 (OOP)
    的编程语言,但它有个弊端就是当需要为多个不具有继承关系的对象引入一个公共行为时,例如日志记录、权限校验、事务管理、统计等功能,只能在每个对象里都引用公共行为,这样做不便于维护,而且有大量重复代码,AOP 的出现弥补了 OOP 的这点不足。
  • 例如:pPam2OP.png
    • 有多少个业务操作,就要写多少重复的校验和日志记录代码,这显然是无法接受的。当然用面向对象的思想,可以把这些重复的代码抽离出来,写成公共方法,就是下面这样。
      pPamMZT.png
    • 代码冗余和可维护性的问题得到了解决,但每个业务方法中依然要依次手动调用这些公共方法,也是略显繁琐。
      有没有更好的方式呢?有的,那就是 AOPAOP 将权限校验、日志记录等非业务代码完全提取出来,与业务代码分离,并寻找节点切入业务代码中。
  • 动态代理:pPaukgs.png

# AOP 的体系结构

  1. AOP 要做的三件事是:
    • 在哪里切入
    • 什么时候切入
    • 切入后做什么事
  2. 在哪里切入:就是权限校验等非业务操作在哪些业务代码中执行。
  3. 什么时候切入:就是业务代码执行前还是执行后。
  4. 切入后做什么事:比如做权限校验,日志记录等等。
  5. pPaMxD1.png
    • Pointcut :切点,决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面),切点分为 execution
      方式和 annotation 方式,前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
    • Advice :处理,包括处理时机和处理内容,处理内容就是要做什么事,比如校验权限和记录日志,处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。
    • Aspect :切面,即 PointcutAdvice
    • JointPoint :连接点,是程序执行的一个点,例如,一个方法的执行或者一个异常处理,在 Spring AOP
      中,一个连接点总是代表一个方法执行。
    • Weaving :织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。

# Aop 原理

  • 🚀动态代理
  • 之前提到 JDK 代理和 Cglib 代理两种动态代理,优秀的 Spring 框架把两种方式在底层都集成了进去,无需担心自己去实现动态生成代理。
  • 那么 Spring 是如何生成代理对象的?创建容器对象的时候,根据切入点表达式拦截的类,生成代理对象。
  • 如果目标对象有实现接口,使用 jdk 代理。如果目标对象没有实现接口,则使用 Cglib 代理。
  • 然后从容器获取代理后的对象,在运行期植入 "切面" 类的方法。
  • 如果目标类没有实现接口,且 class 为 final 修饰的,则不能进行 Spring AOP 编程。
  • 封装了 JDK 代理和 Cglib 代理,当被代理的类实现了接口,则使用 JDK 代理。
  • 如果没有实现接口则使用 Cglib 代理,类没有实现接口且使用 final 修饰 不能使用 AOP。

# AOP 能做什么

  • Spring 声明式事务管理配置
  • Controller 层的参数校验
  • 使用 Spring AOP 实现 MySQL 数据库读写分离案例分析
  • 在执行方法前,判断是否具有权限。
  • 对部分函数的调用进行日志记录,监控部分重要函数,若抛出指定的异常,可以以短信或邮件方式通知相关人员,信息过滤,页面转发等等功能。
  • 什么是事务?
    • 有一系列动作组成的一个单元,这些动作要么全部执行,要么全部不执行。
    • 例子:张三给李四转账。
    • 1. 张三减钱 2. 李四加钱。
    • mysql 是支持事务的。

# Aop 使用

  1. 创建 SpringBoot 项目
  2. 引入 SpringAop 依赖
  3. pom.xml
    <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
  4. 自定义注解
    LogNotes.java
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    /**
    * 自定义 Aop 注解
    * @author LightRain
      */
      @Target({ElementType.METHOD, ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      public @interface LogNotes {
      String value() default "";
      }
  5. Aop 实现类
    LogAspect.java
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    /**
     * 日志 Aop 实现类
     *
     * @author LightRain
     */
    @Slf4j
    @Aspect//@Aspect:把当前类标识为一个切面供容器读取
    @Component// 交由 Spring 容器管理
    public class LogAspect {
        /**
         * &#064;Pointcut:aop 切入点,表示在什么条件下触发
         * &#064;annotation:声明以注解的方式来定义切点,注解为 NeedAuth 的包路径
         */
        @Pointcut("@annotation(com.example.aop.aop.NeedAuth)")//
        public void logPoint() {// 这就是个标志,爱叫啥叫啥,给下面用的
        }
        /**
         * &#064;@Before:前置增强方法的标识,表示在标有注解的方法前执行切面方法
         */
        @Before("logPoint()")
        public void beforeAop() {
            System.out.println("前置通知...");
        }
        /**
         * &#064;After:后置增强方法的标识,表示在标有注解的方法后执行切面方法
         */
        @AfterReturning(value = "logPoint()", returning = "returnValue")
        public void afterAop(Object returnValue) {
            System.out.println("returnValue = " + returnValue);
            System.out.println("后置通知...");
        }
        /**
         * &#064;@Around:环绕增强方法的标识,表示在标有注解的方法前后都执行切面方法
         */
        @Around(value = "logPoint()")
        public Object aroundAop(ProceedingJoinPoint pj) throws Throwable {
            try {
                log.info("{},开始执行!", pj.getSignature());
                System.out.println("环绕通知前...");
    //            pj.proceed ();// 这个有点东西,几个注解的执行顺序有关
                System.out.println("pj.getTarget() = " + pj.getTarget());
                System.out.println("pj.getThis() = " + pj.getThis());
                System.out.println("环绕通知后...");
                System.out.println(pj.getSignature().getName());
                Object[] args = pj.getArgs();
                for (Object arg : args) {
                    System.out.println("参数:" + arg.toString());
                }
                log.info("{},执行完成!", pj.getSignature());
            } catch (Throwable ignored) {
            }
            return pj.proceed();
        }
        @AfterThrowing(value = "logPoint()", throwing = "e")
        public void afterThrowing(Exception e) {
            System.err.println(e.getMessage());
            System.out.println("AOP处理异常");
            Throwable throwable = e.fillInStackTrace();
            System.err.println(throwable);
        }
    }
  6. Aop 测试,在需要切入 Aop 的方法上添加自定义注解即可执行切入
    TestController.java
    import com.example.aop.aop.NeedAuth;
    import com.example.aop.service.TestServiceImpl;
    import jakarta.annotation.Resource;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    /**
     * aop 测试 Controller
     * @author LightRain
     */
    @RestController
    @RequestMapping("/aop")
    public class TestController {
        @Resource
        TestServiceImpl test;
        @RequestMapping("/test")
        @NeedAuth()// 切入点
        public String anoMessage(String value) {
           return test.anoMessage(value);
        }
    }
    TestServiceImpl.java
    /**
     * @author LightRain
     */
    public interface TestServiceImpl {
         String anoMessage(String value);
    }
    TestService.java
    /**
     * @author LightRain
     */
    @Service
    public class TestService implements TestServiceImpl{
        public String anoMessage(String value){
            System.out.println("执行方法needAuth");
            return "测试";
        }
    }