Spring Aop
1.AOP(Aspect-Oriented Programming) 面向切面编程

解耦代码 eg: 有多段类似业务代码,执行时需要进行log输出和判断
@Service
public class UserService {
public void addUser(String name, int age) {
System.out.println("add user: " + name + ", age: " + age);
}
public void deleteUser(int id) throws Exception {
System.out.println("delete user: " + id);
}
}
传统方法:
@Service
public class UserService {
public void addUser(String name, int age) {
System.out.println("[log] method addUser")
System.out.println("add user: " + name + ", age: " + age);
}
public void deleteUser(int id) throws Exception {
logBefore();
System.out.println("delete user: " + id);
logAfter();
}
}
每段业务需要编写对应log输出或者是调用对应的日志输出方法,对业务代码侵入性强,出现了代码冗余.
AOP:
//UserService
@Service
public class UserService {
public void addUser(String name, int age) {
System.out.println("add user: " + name + ", age: " + age);
}
public void deleteUser(int id) throws Exception {
System.out.println("delete user: " + id);
}
}
//LogAspect
@Aspect
@Component
public class LogAspect {
@Pointcut(("within(@org.springframework.stereotype.Service *)"))
// @Around(execution(* com.example.service.*.*(..)))
public void logPoint() {}
@Around("logPoint()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object[] args = joinPoint.getArgs();
System.out.println("[log] 方法Args:" + Arrays.toString(args));
Object proceed = joinPoint.proceed();
long end = System.currentTimeMillis();
System.out.println("[log] 耗时:" + (end - start) + "ms");
return proceed;
}
}
AOP仅需要编写好对应的切面代码,即可监听控制所需要的代码
2. AOP核心概念
| 概念 | 解释 |
|---|---|
| Aspect(切面) | 切断业务逻辑后进入的模块 |
| Pointcut(切入点) | 监听代码切断代码的表达式 |
| JoinPoint(连接点) | 被暂停方法处的上下文信息 |
| Advice(通知) | 在JoinPoint所执行的操作 |
| Weaving(织入) | 将Aspect应用到代码的过程 |
1. PointCut
@Pointcut(表达式)
-
@Pointcut(("within(@org.springframework.stereotype.Service *)"))拦截所有@Service的Bean -
@Pointcut("@annotation(com.example.springBean.RequireAdmin)")拦截springBean下RequireAdmin方法
eg:
@Pointcut(("within(@org.springframework.stereotype.Service *)"))
public void logPoint() {}
@Pointcut("execution(* com.example.springBean.UserService.*(..))")
public void ExceptionAspectLogPoint() {}
@Pointcut("@annotation(com.example.springBean.RequireAdmin)")
public void SecurityAspect() {}
创建切入点后进行重命名,可以简化后续Advice的使用
2. JoinPoint
- JoinPoint JoinPoint不能用在@Around, @Around需要使用 ProceedingJoinPoint
@Before("logPoint()")
public void lMethod(JoinPoint joinPoint) {
// 获取方法名 String
methodName = joinPoint.getSignature().getName();
// 获取参数
Object[] args = joinPoint.getArgs();
}
| 方法 | 说明 | 返回类型 |
|---|---|---|
getSignature() |
获取被调用方法的签名(包含方法名、返回类型等) | Signature |
getArgs() |
获取方法调用时的参数数组 | Object[] |
getTarget() |
获取目标对象(被代理的对象) | Object |
getThis() |
获取代理对象 | Object |
getKind() |
获取连接点的类型(如方法执行、异常处理等) | String |
- ProceedingJoinPoint
@Around("logPoint")
public Object Method(ProceedingJoinPoint joinPoint) throws Throwable {
boolean hasPermission = checkPermission();
if (hasPermission) {
// 有权限,执行目标方法
return joinPoint.proceed();
} else {
// 无权限,不执行目标方法,返回默认值或抛出异常
System.out.println("[Around] No permission, method skipped.");
return null; // 或者 throw new AccessDeniedException();
}
}
新增的关键方法
| 方法名 | 返回类型 | 说明 |
|---|---|---|
proceed() |
Object |
执行目标方法,并返回方法的返回值 |
proceed(Object[] args) |
Object |
执行目标方法,并可以修改传入的参数 |
3.Advice
| Advice 类型 | 执行时机 |
|---|---|
| @Before | 方法执行前 |
| @After | 方法执行后 |
| @AfterReturning | 方法正常返回后 |
| @AfterThrowing | 方法抛出异常后 |
| @Around | 方法执行前后 |
- @Before @After 适用于简单的前后置逻辑
- @AfterReturning @AfterThrowing 处理返回值和异常
- @Around 完全控制,如是否执行 修改返回值 传递参数
方法执行时若匹配到PointCut的表达式,将JointPoint处的方法上下文信息存储,进入Aspect,执行Advice.
3.示例
package com.example.springBean;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogAspect {
@Pointcut(("within(@org.springframework.stereotype.Service *)"))
public void logPoint() {}
@Pointcut("execution(* com.example.springBean.UserService.*(..))")
public void ExceptionAspectLogPoint() {}
@Pointcut("@annotation(com.example.springBean.RequireAdmin)")
public void SecurityAspect() {}
@Around("logPoint()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object[] args = joinPoint.getArgs();
System.out.println("[log] 方法Args:" + Arrays.toString(args));
Object proceed = joinPoint.proceed();
long end = System.currentTimeMillis();
System.out.println("[log] 耗时:" + (end - start) + "ms");
return proceed;
}
@AfterThrowing(pointcut = "ExceptionAspectLogPoint()", throwing = "ex")
public void logException(JoinPoint JoinPoint, Exception ex) {
String methodName = JoinPoint.getSignature().getName();
System.out.println("[ExceptionLog] Method:" + methodName + " 异常信息 :" + ex.getMessage());
}
@Around("SecurityAspect()")
public Object checkSecurity(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
if (UserService.currentUser.equals("admin")) {
System.out.println("[SecurityLog] 当前用户有权限执行方法:" + methodName);
return joinPoint.proceed();
} else {
System.out.println("[SecurityLog] 当前用户无权限执行方法:" + methodName);
throw new SecurityException("当前用户无权限执行方法:" + methodName);
}
}
}
package com.example.springBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public static String currentUser = "admin";
@RequireAdmin
public void addUser(String name, int age) {
System.out.println("add user: " + name + ", age: " + age);
}
public void deleteUser(int id) throws Exception {
if (id <= 0) {
throw new Exception("Invalid user ID");
}
System.out.println("delete user: " + id);
}
}
package com.example.springBean;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireAdmin {
String value() default "admin";
}