什么是aop
AOP(面向切面编程)
AOP是一种编程范式,它通过将横切关注点(如日志、安全、事务等)从主要业务逻辑中分离,降低模块间的耦合度,提升代码的可重用性和可维护性。AOP可以看作是对传统面向对象编程(OOP)的补充,尤其是在处理那些跨多个类的公共逻辑时。
使用场景
AOP常用于以下场景:
- 日志记录:集中管理日志的记录逻辑。
- 性能监控:统一监控方法的执行时间。
- 安全控制:在方法执行前进行权限检查。
- 事务管理:管理数据库事务的开始和结束。
- 异常处理:集中处理异常逻辑。
- 资源管理:如连接池的管理。
为什么需要AOP
在OOP中,公共功能的实现往往导致代码重复和维护困难。通过AOP,开发者可以将这些横切关注点提取到单独的切面中,从而避免重复代码,提高代码的整洁性和可维护性。例如,日志代码可以集中在一个切面中,而不是散布在每个业务方法中,这样在需要修改日志逻辑时,只需在一个地方进行更新。
技术要点
通知(Advice):
- 通知定义了“什么时候”和“做什么”。可以理解为在某个连接点执行的具体逻辑。通知类型包括:
- 前置通知(Before):在目标方法执行之前运行。
- 后置通知(After):在目标方法执行之后运行。
- 环绕通知(Around):在目标方法执行前后都能运行,允许控制方法的执行。
- 异常通知(After Throwing):在目标方法抛出异常时执行。
- 返回通知(After Returning):在目标方法正常返回后执行。
连接点(Join Point):
- 连接点是程序执行过程中的一个特定位置,任何可以被通知拦截的点。例如,方法调用、对象创建等都是连接点。
切点(Pointcut):
- 切点是定义在哪些连接点上应用通知的表达式。切点用于过滤连接点,指定哪些方法或类会受到通知的影响。
切面(Aspect):
- 切面是通知和切点的结合,封装了横切关注点的逻辑。它定义了在哪些连接点上执行哪些通知,形成完整的逻辑。
引入(Introduction):
- 引入允许在不修改现有类的情况下,向其添加新方法或属性。这使得现有类可以具备额外的功能,增加了灵活性。
织入(Weaving):
- 织入是将切面应用到目标对象的过程。织入可以发生在不同的时间点:
- 编译期织入:在编译阶段将切面集成到目标类中。
- 类加载期织入:在类被加载到JVM时进行织入。
- 运行期织入:在程序运行时进行织入,通常使用动态代理实现。
整合使用
导入依赖
在springboot中使用aop要导aop依赖
<!--aop 切面-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
创建服务类
创建一个服务类,定义业务逻辑:
package com.example.demo.Service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public String createUser() {
return "用户创建成功";
}
}
创建切面类
使用AOP定义切面,添加通知:
package com.example.demo.Aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.demo.controller.*.*(..))")
public void logBefore() {
System.out.println("目标方法即将被调用");
}
@After("execution(* com.example.demo.controller.*.*(..))")
public void logAfter() {
System.out.println("目标方法已执行");
}
}
创建Controller
创建一个Controller,通过HTTP请求调用服务:
package com.example.demo.controller;
import com.example.demo.Service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/create")
public String createUser() {
return userService.createUser();
}
}
运行DemoApplication,在Postman访问http://localhost:8080/users/create,
不出意外,控制台输出如图所示:
定义通知
前置通知(@Before):在目标方法调用之前执行。适用于执行前的准备工作,如日志记录、权限检查等。
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("目标方法即将被调用");
}
后置通知(@After):在目标方法完成之后执行,不论目标方法是否成功。通常用于清理工作或收尾日志。
@After("execution(* com.example.service.*.*(..))")
public void logAfter() {
System.out.println("目标方法已执行");
}
环绕通知(@Around):在目标方法调用前后执行,允许控制方法的执行,可以选择是否调用目标方法。
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("目标方法执行前");
Object result = joinPoint.proceed(); // 调用目标方法
System.out.println("目标方法执行后");
return result;
}
返回通知(@AfterReturning):在目标方法成功执行之后执行。可用于获取方法返回值,进行进一步处理。
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(Object result) {
System.out.println("目标方法成功返回,结果为: " + result);
}
异常通知(@AfterThrowing):在目标方法抛出异常后执行。可用于处理异常情况,记录错误信息。
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
public void logAfterThrowing(Exception ex) {
System.out.println("目标方法抛出异常: " + ex.getMessage());
}
Spring Boot AOP 实践案例
下面是一个使用 Spring Boot AOP 的简单实践案例,我们将使用 AOP 来记录方法的执行时间。
创建服务类
创建一个服务类,包含要监控的方法:
package com.example.demo.Service;
import org.springframework.stereotype.Service;
/**
* 用户服务类
* 提供用户创建和删除的功能
*/
@Service
public class UserService {
/**
* 创建用户
*
* @return 创建成功的信息
* @throws InterruptedException 如果模拟执行时间过程中被中断
*/
public String createUser() throws InterruptedException {
// 模拟方法执行时间
Thread.sleep(1000);
return "用户创建成功";
}
/**
* 删除用户
*
* @return 删除成功的信息
* @throws InterruptedException 如果模拟执行时间过程中被中断
*/
public String deleteUser() throws InterruptedException {
// 模拟方法执行时间
Thread.sleep(500);
return "用户删除成功";
}
}
创建切面类
定义切面,用于记录方法执行时间:
package com.example.demo.Aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* 用于记录服务层方法执行时间的切面类
*/
@Aspect
@Component
public class PerformanceLoggingAspect {
/**
* 记录目标方法的执行时间
*
* @param joinPoint 切入点对象,包含了目标方法的信息
* @return 目标方法的执行结果
* @throws Throwable 如果目标方法执行过程中抛出异常,则抛出Throwable
*/
@Around("execution(* com.example.demo.Service.*.*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 记录开始时间
long start = System.currentTimeMillis();
// 执行目标方法
Object proceed = joinPoint.proceed();
// 计算方法执行时间
long executionTime = System.currentTimeMillis() - start;
// 打印方法执行时间
System.out.println(joinPoint.getSignature() + " 耗时: " + executionTime + "毫秒");
return proceed;
}
}
package com.example.demo.Aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.springframework.stereotype.Component;
/**
* 切面类,用于在方法执行前后记录日志
*/
@Aspect
@Component
public class LoggingAspect {
/**
* 在目标方法执行之前记录日志
*/
@Before("execution(* com.example.demo.controller.*.*(..))")
public void logBefore() {
System.out.println("目标方法即将被调用");
}
/**
* 在目标方法执行之后记录日志
*/
@After("execution(* com.example.demo.controller.*.*(..))")
public void logAfter() {
System.out.println("目标方法已执行");
}
}
创建Controller
创建一个Controller来调用服务方法:
package com.example.demo.controller;
import com.example.demo.Service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 用户控制器类,处理与用户相关的HTTP请求
*/
@RestController
@RequestMapping("/users")
public class UserController {
// 用户服务实例,用于处理用户相关的业务逻辑
private final UserService userService;
/**
* UserController构造函数
* @param userService 用户服务实例
*/
public UserController(UserService userService) {
this.userService = userService;
}
/**
* 创建用户
*
* @return 创建用户的结果
* @throws InterruptedException 如果用户创建过程中被中断
*/
@GetMapping("/create")
public String createUser() throws InterruptedException {
return userService.createUser();
}
/**
* 删除用户
*
* @return 删除用户的结果
* @throws InterruptedException 如果用户删除过程中被中断
*/
@GetMapping("/delete")
public String deleteUser() throws InterruptedException {
return userService.deleteUser();
}
}
通过浏览器或Postman访问:
创建用户:
http://localhost:8080/users/create
删除用户:
http://localhost:8080/users/delete
控制台会输出以下内容: