在当今的软件开发领域,提高代码的可维护性、可扩展性以及减少重复代码是至关重要的。Spring AOP(Aspect Oriented Programming,面向切面编程)作为一种强大的编程思想和技术,在解决这些问题上发挥着重要作用。本文将结合实际代码示例,深入探讨 Spring AOP 的相关知识,帮助大家更好地掌握这一技术。
AOP 即面向切面编程,它可以简单理解为面向特定方法编程。在实际开发中,当部分业务方法运行较慢,需要定位执行耗时较长的接口时,就可以利用 AOP 来统计每一个业务方法的执行耗时。比如在一个部门管理系统中,有获取部门列表、删除部门、根据 ID 获取部门等业务方法,若要统计这些方法的执行耗时,使用 AOP 能高效地实现这一需求。
以统计所有部门管理业务层方法的执行耗时为例,在未使用 AOP 时,需要在每个业务方法中重复编写获取开始时间、运行原始方法、获取结束时间并计算耗时的代码,这样会导致大量重复代码。
pom.xml
文件中引入 Spring AOP 的依赖。
org.springframework.boot
spring-boot-starter-aop
@Aspect
和@Component
注解,在方法上使用@Around
等通知注解,并编写切入点表达式。import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class RecordTimeAspect {
@Around("execution(* com.itheima.service.impl.*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
long beginTime = System.currentTimeMillis();
Object result = pjp.proceed();
long endTime = System.currentTimeMillis();
log.info("执行耗时: {} ms", endTime - beginTime);
return result;
}
}
在上述代码中,@Around
环绕通知会在目标方法执行前后都执行。通过ProceedingJoinPoint
的proceed
方法调用原始方法,获取方法的返回值,并计算执行耗时进行日志记录。
根据通知方法执行时机的不同,常见的通知类型有以下五类:
ProceedingJoinPoint.proceed()
来执行原始方法,方法返回值必须指定为Object
来接收原始方法的返回值。execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws异常?)
,其中带?
的部分可以省略。可以使用通配符*
(单个独立的任意符号)和..
(多个连续的任意符号),还可以使用&&
、||
、!
来组合复杂的切入点表达式。例如:@Before("execution(public void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
public void before(JoinPoint joinPoint) {}
@Around("@annotation(com.itheima.anno.Logoperation)")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {}
当有多个切面的切入点都匹配到目标方法时,默认按照切面类的类名字母排序执行通知方法。也可以使用@Order(数字)
加在切面类上来控制顺序,数字小的先执行目标方法前的通知,数字小的后执行目标方法后的通知。
将系统中增、删、改相关接口的操作日志记录到数据库表中,日志信息包含操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长。
@Around
环绕通知,因为它可以在方法执行前后获取相关信息,方便记录日志。save
、delete
、update
,切入点表达式可以写成:@Around("execution(* com.itheima.controller.*.save(..)) || execution(* com.itheima.controller.*.delete(..)) || execution(* com.itheima.controller.*.update(..))")
ProceedingJoinPoint
获取目标类名、方法名、方法参数等信息。@Around("execution(* com.itheima.controller.*.save(..)) || execution(* com.itheima.controller.*.delete(..)) || execution(* com.itheima.controller.*.update(..))")
public Object logOperation(ProceedingJoinPoint joinPoint) throws Throwable {
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
// 这里可以将日志信息存入数据库,实际代码需要引入数据库操作相关依赖和代码
return result;
}
TokenFilter
中解析 jwt 令牌获取当前登录员工的 ID,利用ThreadLocal
将 ID 传递给 AOP 程序、Controller、Service。// 定义ThreadLocal操作的工具类
public class LoginEmployeeUtil {
private static final ThreadLocal loginEmployeeId = new ThreadLocal<>();
public static void setLoginEmployeeId(Long id) {
loginEmployeeId.set(id);
}
public static Long getLoginEmployeeId() {
return loginEmployeeId.get();
}
public static void removeLoginEmployeeId() {
loginEmployeeId.remove();
}
}
// 在TokenFilter中解析完ID后存入ThreadLocal
public class TokenFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 解析jwt令牌获取员工ID
Long employeeId = parseJwtToken(servletRequest);
LoginEmployeeUtil.setLoginEmployeeId(employeeId);
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
LoginEmployeeUtil.removeLoginEmployeeId();
}
}
}
// 在AOP程序中从ThreadLocal获取当前登录员工的ID
@Around("execution(* com.itheima.controller.*.save(..)) || execution(* com.itheima.controller.*.delete(..)) || execution(* com.itheima.controller.*.update(..))")
public Object logOperation(ProceedingJoinPoint joinPoint) throws Throwable {
Long employeeId = LoginEmployeeUtil.getLoginEmployeeId();
// 其他记录日志操作...
return joinPoint.proceed();
}
通过以上步骤,我们就可以基于 Spring AOP 实现记录系统增、删、改功能接口的操作日志。
Spring AOP 在开发中有着广泛的应用场景,如记录系统操作日志、权限控制、事务管理等。通过本文对 Spring AOP 基础概念、快速入门、进阶知识以及案例实战的介绍,希望大家对 Spring AOP 有更深入的理解和掌握,能够在实际项目中灵活运用这一技术,提高代码的质量和开发效率。