本文已参与「新人创作礼」活动,一起开启掘金创作之路。
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 的功能或者将对应的方法织入流程。
使用 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 {
...
}