Spring Boot 学习笔记 03——约定编程:Spring AOP

346 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Spring AOP (Aspect Oriented Programming, 面向切面编程) 是一种约定流程的编程。按照一定的规则,就可以将代码织入事先约定的流程中。AOP 最为典型的实际应用就是数据库事务的管理。

Spring AOP 可以处理一些无法使用 OOP 实现的业务逻辑。其次,通过约定,可以将一些业务逻辑织入流程中,并且可以将一些通用的逻辑抽取出来,然后给予默认实现,这样只需要完成部分的功能,使得开发者的代码更加简短,同时可维护性也得到提高。

AOP 术语和流程

  • 连接点(join point): 对应的是具体被拦截的对象,因为 Spring 只能支持方法,所以被拦截的对象往往就是指特定的方法,AOP 将通过动态代理技术把它织入对应的流程中。
  • 切点(point cut): 有时候,我们的切面不单单应用于单个方法,也可能是多个类的不同方法,这时,可以通过正则式和指示器的规则去定义,从而适配连接点。切点就是提供这样一个功能的概念。
  • 通知(advice): 就是按照约定的流程下的方法,分为前置通知(before advice)、后置通知(after advice)、环绕通知(around advice)、事后返回通知(afterReturning advice)和异常通知(afterThrowing advice),它会根据约定织入流程中,需要弄明白它们在流程中的顺序和运行的条件。
  • 目标对象(target): 即被代理对象。
  • 引入(introduction): 是指引入新的类和其方法,增强现有 Bean 的功能。
  • 织入(weaving): 它是一个通过动态代理技术,为原有服务对象生成代理对象,然后将与切点定义匹配的连接点拦截,并按约定将各类通知织入约定流程的过程。
  • 切面(aspect): 是一个可以定义切点、各类通知和引入的内容,Spring AOP 将通过它的信息来增强 Bean 的功能或者将对应的方法织入流程。
Spring AOP 流程约定

使用 AOP 开发

确定连接点

UserServiceImpl.java

package com.springboot.chapter4.aspect.service.impl;
/**** imports ****/

@Service
public class UserServiceImpl implements UserService {
  @Override
  public void printUser(User user) {
    if (user == null) {
      throw new RuntimeException("检查用户参数是否为空......");
    }
    System.out.print("id = " + user.getId());
    System.out.print("\tusername = " + user.getUsername());
    System.out.println("\tnote = " + user.getNote());
  }
}

切点定义

MyAspect.java

package com.springboot.chapter4.aspect;
/**** imports ****/

@Aspect
public class MyAspect {

  @Pointcut("execution(*com.springboot.chapter4.aspect.service.impl.UserServiceImpl.printUser(..))")
  public void pointCut() {
  }

  @Before("pointCut()")
  public void before() {
    System.out.println("before......");
  }

  @After("pointCut()")
  public void after() {
    System.out.println("after......");
  }

  @AfterReturning("pointCut()")
  public void afterReturning() {
    System.out.println("afterReturning......");
  }

  @AfterThrowing("pointCut()")
  public void afterThrowing() {
    System.out.println("afterThrowing......");
  }
}

测试 AOP

UserController.java

package com.springboot.chapter4.aspect.controller;
/**** imports ****/

// 定义控制器
@Controller
// 定义类请求路径
@RequestMapping("/user")
public class UserController {

  // 注入用户服务
  @Autowired
  private UserService userService = null;

  // 定义请求
  @RequestMapping("/print")
  // 转换为 JSON
  @ResponseBody
  public User printUser(Long id, String userName, String note) {
    User user = new User();
    user.setId(id);
    user.setUsername(userName);
    user.setNote(note);
    userService.printUser(user); // 若 user = nul1,则执行 afterThrowing 方法
    return user;// 加入断点
  }
}

Chapter4Application.java

package com.springboot.chapter4.main;
/**** imports ****/

// 指定扫描包
@SpringBootApplication(scanBasePackages = {"com.springboot.chapter4.aspect"})
public class Chapter4Application {

  // 定义切面
  @Bean(name = "myAspect")
  public MyAspect initMyAspect() {
    return new MyAspect();
  }
  
  // 启动切面
  public static void main(String[]args) {
    SpringApplication.run(Chapter4Application.class, args);
  }
}

打印日志:

before......
id = 1 username = user_name_1 note = 2323
after......
afterReturning......

环绕通知

MyAspect.java 加入环绕通知:

@Around("pointCut()")
public void around(ProceedingJoinPoint jp) throws Throwable {
  System.out.println("around before......");
  // 回调目标对象的原有方法
  jp.proceed();
  System.out.println("around after......");
}

多个切面

创建切面实例

定义多个切面类后,在启动类中创建实例:

// 指定扫描包
@SpringBootApplication(scanBasePackages = {"com.springboot.chapter4.aspect"})
public class Chapter4Application {

  // 定义切面
  @Bean(name = "myAspect2")
  public MyAspect2 initMyAspect2() {
    return new MyAspect2();
  }

  // 定义切面
  @Bean(name = "myAspect1")
  public MyAspect1 initMyAspect1() {
    return new MyAspect1();
  }

  //定义切面
  @Bean(name = "myAspect3")
  public MyAspect3 initMyAspect3() {
    return new MyAspect3();
  }

  // 启动 Spring Boot
  public static void main(String[]args) {
    SpringApplication.run(Chapter4Application.class,args);
  }
}

指定多个切面的顺序

@Aspect
@Order(1) // 数字越小,优先级越高
public class MyAspect1 {
  ...
}