AOP是一种编程范式,主要用于增强代码的可维护性和复用性,通过横切关注点来解耦业务逻辑和通用功能(如日志、权限验证、事务管理等)。
AOP 的核心概念
- 切面(Aspect):封装通用功能(如日志、事务、权限等)的模块。
- 通知(Advice):切面中定义的具体操作,如方法执行前后插入代码。
- 切点(Pointcut):定义在哪些地方插入通知,比如对某些类或方法进行拦截。
- 目标对象(Target):被 AOP 代理的业务类。
- 代理(Proxy):AOP 通过动态代理机制增强目标对象的方法执行。
- 织入(Weaving):将切面应用到目标对象的过程,Spring 使用动态代理实现。
举个例子:用 AOP 记录日志
1. 传统方式(不使用 AOP)
如果我们需要在多个方法中添加日志记录代码,代码会变得冗余且难以维护:
public class OrderService {
public void createOrder() {
System.out.println("日志:创建订单");
System.out.println("订单创建成功");
}
public void cancelOrder() {
System.out.println("日志:取消订单");
System.out.println("订单取消成功");
}
}
如果未来需要修改日志逻辑,就要修改所有方法,这显然不符合开闭原则(OCP)。
2. 使用 Spring AOP
通过 AOP,我们可以把日志代码从业务逻辑中剥离出来,让 OrderService 只关注业务逻辑,而日志由切面自动处理。
(1)引入 AOP 依赖(Spring Boot 项目)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
(2)定义业务类
import org.springframework.stereotype.Service;
@Service
public class OrderService {
public void createOrder() {
System.out.println("订单创建成功");
}
public void cancelOrder() {
System.out.println("订单取消成功");
}
}
(3)定义切面(Aspect)
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.OrderService.*(..))") // 切点:拦截 OrderService 的所有方法
public void logBeforeMethod() {
System.out.println("日志:方法执行前记录日志");
}
}
(4)测试效果
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AopExampleApplication implements CommandLineRunner {
@Autowired
private OrderService orderService;
public static void main(String[] args) {
SpringApplication.run(AopExampleApplication.class, args);
}
@Override
public void run(String... args) {
orderService.createOrder();
orderService.cancelOrder();
}
}
(5)输出结果
日志:方法执行前记录日志
订单创建成功
日志:方法执行前记录日志
订单取消成功
AOP 例子:权限校验
在实际开发中,很多方法需要进行权限校验,比如用户是否登录、是否有某个角色等。
如果我们在每个业务方法中手动添加权限检查代码,会导致代码冗余、可维护性差。
AOP 允许我们将权限校验逻辑从业务代码中解耦,提升代码的整洁度和复用性。
传统方式(未使用 AOP)
假设 UserService
需要检查用户是否有权限:
public class UserService {
public void getUserInfo(String username) {
if (!checkPermission()) {
throw new RuntimeException("无权限访问");
}
System.out.println("查询用户信息:" + username);
}
private boolean checkPermission() {
// 假设这里是权限检查逻辑
return Math.random() > 0.5; // 50% 概率通过
}
}
问题:
- 每个方法都需要写
checkPermission()
,代码冗余。 - 如果权限逻辑修改,需要修改多个地方。
使用 AOP 实现权限校验
(1)定义业务类
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void getUserInfo(String username) {
System.out.println("查询用户信息:" + username);
}
public void deleteUser(String username) {
System.out.println("删除用户:" + username);
}
}
此时业务方法不关心权限问题,AOP 会自动处理。
(2)定义权限切面
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 AuthAspect {
@Around("execution(* com.example.service.UserService.*(..))") // 拦截 UserService 所有方法
public Object checkPermission(ProceedingJoinPoint joinPoint) throws Throwable {
if (!hasPermission()) {
throw new RuntimeException("无权限访问:" + joinPoint.getSignature().getName());
}
System.out.println("权限校验通过:" + joinPoint.getSignature().getName());
return joinPoint.proceed(); // 继续执行目标方法
}
private boolean hasPermission() {
// 模拟权限校验(50% 通过)
return Math.random() > 0.5;
}
}
核心逻辑:
@Around
注解:拦截UserService
的所有方法。hasPermission()
:模拟权限校验,50% 通过。joinPoint.proceed()
:权限通过后执行原方法,否则抛异常。
(3)测试效果
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AopAuthApplication implements CommandLineRunner {
@Autowired
private UserService userService;
public static void main(String[] args) {
SpringApplication.run(AopAuthApplication.class, args);
}
@Override
public void run(String... args) {
try {
userService.getUserInfo("Alice");
} catch (Exception e) {
System.out.println(e.getMessage());
}
try {
userService.deleteUser("Bob");
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
(4)运行结果
可能的输出:
无权限访问:getUserInfo
权限校验通过:deleteUser
删除用户:Bob
或者
权限校验通过:getUserInfo
查询用户信息:Alice
无权限访问:deleteUser
解释:
- 由于
hasPermission()
是随机的,可能某个方法被拦截,也可能通过。 - 如果拦截,
AOP
直接抛出"无权限访问"
异常,业务方法不会执行。