SSM 学习第三天

AOP

AOP: 面向切面编程

  • 连接点(JoinPoint) 程序执行过程中的任意位置, 粒度为执行方法, 抛出异常, 设置变量等.
    • 在 SpringAOP 中, 理解为方法的执行
  • 切入点(PointCut) 一个切入点可以只描述一个方法, 也可以匹配多个方法
  • 通知(Advice) 在切入点处执行的方法, 也就是共性功能
    • 在 SpringAOP 中, 功能最终以方法的形式呈现
  • 通知类: 定义通知的类
  • 切面(Aspect) 描述通知与切入点的对应关系

入门案例

  • 资源目录
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    .
    ├── java
    │   └── com
    │   └── ljguo
    │   ├── App.java
    │   ├── aop
    │   │   └── MyAdvice.java
    │   ├── config
    │   │   └── SpringConfig.java
    │   └── dao
    │   ├── BookDao.java
    │   └── impl
    │   └── BookDaoImpl.java
    └── resources
  • pom.xml 导入依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
    </dependency>

    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
    </dependency>
  • MyAdvice
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package com.ljguo.aop;

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

    @Component
    @Aspect
    public class MyAdvice {

    @Pointcut("execution(void com.ljguo.dao.impl.BookDaoImpl.update())")
    private void pointcut(){}

    @Before("pointcut()")
    public void method() {
    System.out.println(System.currentTimeMillis());
    }
    }
  1. @Aspect 注解用于将该类定义为切面
  2. @Pointcu 注解用于定义一个切入点方法, 后面需要跟上一个空方法
  3. execution(void com.ljguo.dao.impl.BookDaoImpl.update()) 叫做切入点表达式, 表示 void(空返回类型), com.ljguo.dao.impl.BookDaoImpl 类中的 update 空参方法
  4. @Before 注解用于定义一个通知方法, 在切入点方法之前执行
  • SpringConfig
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.ljguo.config;

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

    @Configuration
    @ComponentScan("com.ljguo")
    @EnableAspectJAutoProxy
    public class SpringConfig {
    }
    @EnableAspectJAutoProxy 注解用于表示这是一个用注解开发的 AOP
  • BookDao
    1
    2
    3
    4
    5
    6
    package com.ljguo.dao;

    public interface BookDao {
    public void save();
    public void update();
    }
  • BookDaoImpl
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.ljguo.dao.impl;

    import com.ljguo.dao.BookDao;
    import org.springframework.stereotype.Repository;

    @Repository
    public class BookDaoImpl implements BookDao {

    public void save() {
    System.out.println("book dao save...");
    }

    public void update() {
    System.out.println("book dao update...");
    }
    }
  • App
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package com.ljguo;

    import com.ljguo.config.SpringConfig;
    import com.ljguo.dao.BookDao;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;

    public class App {
    public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
    BookDao bookDao = ac.getBean(BookDao.class);
    bookDao.update();
    }
    }

结果

运行 App 后得到

1
2
1672128872130
book dao update...

深入 AOP

核心本质: 代理

AOP 工作流程

  1. Spring 容器启动
  2. 读取所有切面配置中的切入点(没有配置的切入点不被读取)
  3. 初始化 Bean, 判断 bean 中对应的类中方法是否匹配到任意切入点
    • 匹配失败, 创建对象
    • 匹配成功, 创建原始对象的代理对象
      使用代理对象来实现增强功能, 切片
  4. 获取 bean 执行方法
    • 获取 bean, 调用方法, 完成操作
    • 获取的 bean 是代理对象时, 根据代理对象的运行模式运行原始方法与增强内容, 完成操作

切入点表达式

  • 介绍
    • 切入点: 要增强的方法
    • 切入点表达式: 要进行增强的方法的描述方式
  • 标准格式
    • 动作关键字: 描述切入点的行为动作, 如 execution 表示执行到指定切入点
    • 访问修饰符, 可以省略
    • 返回值
    • 包名
    • 类名/接口
    • 方法名
    • 参数
    • 异常名, 可以省略
  • 通配符
    • * : 单个独立的任意符号,如:
      1
      execution (public * com.ljguo.*.UserService.select* (*))
    • .. : 多个连续任意符号, 如:
      1
      execution (public void com.ljguo.dao.UserDao.add(..))
    • + : 专用于匹配子类类型
      1
      execution (* *..*Service+.*(..))

AOP 通知类型

AOP 的通知类型分为 5 种类型

  • 前置通知: @Before
  • 后置通知: @After
  • 环绕通知: @Around
  • 返回后通知: @AfterReturning
  • 抛出异常后通知: @AfterThrowing

主要掌握环绕通知:

  • 环绕通知需要依赖形参 ProceedingJionPoint 才能实现对原始方法的调用, 如果没使用, 将会跳过原始方法的执行
  • 如果原始方法没有返回值, 那么通知方法可以设置为 void 返回值类型, 如果有, 则必须设置为 Object 类型返回值
  • 可以使用形参 pjp 调用 peoceed 方法, 执行原始方法并返回值
  • 由于无法预知原始方法是否有异常, 所以环绕通知方法必须抛异常

环绕通知详解

1
2
3
4
5
6
7
@Around("pointcut()")
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("before");
Object proceed = pjp.proceed();
Signature signature = pjp.getSignature();
System.out.println("after");
}
  • pjp.getSignature 获取到一个 Signature 对象, 可以通过该对象得到一些原始方法的属性, 例如
    1
    2
    3
    4
    signature.getClass();
    signature.getDeclaringType();
    signature.getDeclaringTypeName();
    signature.getName();