AOP-深入了解

案例设定

  • 需求: 测定接口的执行效率
  • 简化: 在接口执行前输出当前系统时间
  • 开发模式: XML or 注解

思路分析

  1. 导入坐标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.11.RELEASE</version>
    </dependency>

    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
    </dependency>
    </dependencies>
  2. 制作连接点方法(原始操作,Dao接口与实现类)

    1
    2
    3
    4
    public interface BookDao {
    void save();
    void update();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    @Repository
    public class BookDaoImpl implements BookDao {
    public void save() {
    System.out.println("dao save...................");
    }

    public void update() {
    System.out.println("dao update...................");

    }
    }

  3. 制作共性功能(通知类与通知)

  4. 定义切入点

    通配符含义
    在这里插入图片描述
  5. 绑定切入点与通知关系

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Aspect
    @Component
    public class MyAdvice {
    /**
    * @Before("execution(* com.example..*(..))")
    * "*": 返回值类型
    * com.example..*(..): com.example..【.. 表示com.example包及其子包的路径】 *: 任意公共方法 (..【表示任意多个参数,】)
    */
    @Before("execution(* com.example..*(..))")
    public void showTime() {
    System.out.println(System.currentTimeMillis());
    }
    }
  6. 配置类

    1
    2
    3
    4
    5
    6
    7

    @Configuration
    @ComponentScan("com.example")
    @EnableAspectJAutoProxy
    public class SpringConfig {
    }

  7. 启动测试

    1
    2
    3
    4
    5
    6
    7
    public class Application {
    public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    BookDao dao = ctx.getBean(BookDao.class);
    dao.save();
    }
    }
    输出期望结果

AOP-工作流程

  1. Spring 容器启动
  2. 读取所有切面配置中的切入点
  3. 初始化bean,判定bean 对应的类中的方法是否匹配到任意切入点
    • 匹配失败,创建对象
    • 匹配成功,创建原始对象【目标对象】代理对象
  4. 获取bean 执行方法
    • 获取bean,调用方法并执行,完成操作
    • 获取的bean 是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

概念

  • 目标对象(Target): 原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
  • 代理(Proxy): 目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现

AOP-切入点表达式

  • 书写技巧
    • 所有代码按照标准规范开发,否则以下技巧全部实现
    • 描述切入点通常描述接口,而不描述实现类
    • 访问控制修饰符针对接口开发均采用public 描述(可省略访问控制修饰符描述)
    • 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用* 统配快速描述
    • 包名书写尽量不使用 .. 匹配,效率过低,常用* 做单个包描述匹配,或精准匹配
    • 接口名/类名书写名称与模块相关的采用 * 匹配,例如UserService 书写成*Service,绑定业务层接口名
    • 方法名书写以动词进行精准匹配,名词采用* 匹配,例如getById 书写成getBy*,selectAll 书写成selectAll
    • 参数规则较为复杂,根据业务方法灵活调整
    • 通常不使用异常作为匹配规则

SpringBoot-AOP

  • 目标

    注解实现增强
  • 实现思路

    • 创建项目,修改pom.xml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <dependencies>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>

      <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
      </dependency>

      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      </dependency>
      <!-- AOP -->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
      </dependency>
      </dependencies>
    • 创建注解

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      package com.example.aopboot.demo.annotation;

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

      @Target(ElementType.METHOD) // 修饰方法
      @Retention(RetentionPolicy.RUNTIME) // 作用范围
      @Documented
      public @interface Log {
      }


      @Documented 是一个标记注解,没有成员变量。 用@Documented 注解修饰的注解类会被JavaDoc 工具提取成文档。 默认情况下,JavaDoc 是不包括注解的,但如果声明注解时指定了@Documented,就会被JavaDoc 之类的工具处理,所以注解类型信息就会被包括在生成的帮助文档中。
    • 创建通知

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26

      @Aspect
      @Component
      public class LogAdvice {
      /**
      * 定义切点:
      *
      * @annotation: 注解形式
      * (com.example.aopboot.demo.annotation.Log)
      */
      @Pointcut("@annotation(com.example.aopboot.demo.annotation.Log)")
      public void point() {
      }

      @Before("point()")
      public void adviceWeb() {
      System.out.println("load class file,place wait............................");
      System.out.println(".");
      System.out.println("...");
      System.out.println("......");
      System.out.println(".........");
      System.out.println(".............");
      System.out.println("load finish............................");
      }
      }

      区别
    • 使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      import com.example.aopboot.demo.annotation.Log;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RestController;

      @RestController
      public class HelloWorld {
      @Log
      @GetMapping("/hello")
      public String sayHello() {
      return "say hello............";
      }
      }