Spring AOP 详解
AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架的重要特性,它允许开发者将横切关注点(如日志、事务、安全等)从业务逻辑中分离出来,实现关注点的模块化。
Spring AOP = 关注点分离 + 代码复用 + 模块化设计 + 动态代理
- 🔍 关注点分离:将横切关注点与业务逻辑解耦
- 🔄 代码复用:消除重复代码,提高可维护性
- 📦 模块化设计:独立管理通用功能,易于扩展
- 🔧 动态代理:运行时动态增强,无侵入性
1. AOP基础概念
1.1 什么是AOP?
AOP是一种编程范式,它通过预编译方式和运行期动态代理实现程序功能的统一维护。AOP的核心思想是将横切关注点从主业务逻辑中分离出来。
传统编程 vs AOP编程
- 传统编程方式
- AOP编程方式
1// 传统方式 - 业务逻辑和横切关注点混合2public class UserService {3 public void createUser(User user) {4 // 日志记录5 System.out.println("开始创建用户: " + user.getName());6 7 // 业务逻辑8 userRepository.save(user);9 10 // 日志记录11 System.out.println("用户创建完成: " + user.getName());12 }13 14 public void updateUser(User user) {15 // 日志记录16 System.out.println("开始更新用户: " + user.getName());17 18 // 业务逻辑19 userRepository.update(user);20 21 // 日志记录22 System.out.println("用户更新完成: " + user.getName());23 }24}传统方式的缺点
- 横切关注点代码与业务逻辑混合
- 代码重复,难以维护
- 职责不明确,违反单一职责原则
- 修改横切逻辑需要修改多处代码
1// AOP方式 - 分离关注点2@Service3public class UserService {4 public void createUser(User user) {5 // 纯业务逻辑6 userRepository.save(user);7 }8 9 public void updateUser(User user) {10 // 纯业务逻辑11 userRepository.update(user);12 }13}1415@Aspect16@Component17public class LoggingAspect {18 @Around("execution(* com.example.service.*.*(..))")19 public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {20 System.out.println("开始执行: " + joinPoint.getSignature().getName());21 Object result = joinPoint.proceed();22 System.out.println("执行完成: " + joinPoint.getSignature().getName());23 return result;24 }25}AOP方式的优点
- 关注点分离,业务逻辑更纯粹
- 避免代码重复,提高可维护性
- 集中管理横切关注点
- 非侵入式,不修改原有业务代码
1.2 AOP核心概念
AOP核心概念
| 概念 | 说明 | 示例 |
|---|---|---|
| Aspect(切面) | 横切关注点的模块化 | @Aspect注解的类 |
| Join Point(连接点) | 程序执行过程中的某个特定点 | 方法调用、异常抛出 |
| Pointcut(切点) | 匹配连接点的表达式 | execution(* com.example.service.*.*(..)) |
| Advice(通知) | 在切点处要执行的代码 | @Before、@After、@Around |
| Target Object(目标对象) | 被代理的对象 | 业务服务类 |
| Proxy(代理) | AOP框架创建的对象 | Spring自动创建 |
| Weaving(织入) | 将切面应用到目标对象的过程 | 编译时、类加载时、运行时 |
AOP概念示例代码
1// 1. Aspect(切面)2@Aspect3@Component4public class LoggingAspect {5 // 切面实现6}78// 2. Pointcut(切点)9@Pointcut("execution(* com.example.service.*.*(..))")10public void serviceMethods() {}1112// 3. Advice(通知)13@Before("serviceMethods()")14public void beforeAdvice() {15 System.out.println("方法执行前");16}1718// 4. Target Object(目标对象)19@Service20public class UserService {21 // 目标对象22}2. AOP实现原理
2.1 动态代理机制
Spring AOP基于动态代理实现,支持JDK动态代理和CGLIB代理:
- JDK动态代理
- CGLIB代理
1public class JdkDynamicProxyExample {2 3 public static void main(String[] args) {4 UserService userService = new UserService();5 6 // 创建代理对象7 UserService proxy = (UserService) Proxy.newProxyInstance(8 UserService.class.getClassLoader(),9 new Class<?>[]{UserService.class},10 new LoggingInvocationHandler(userService)11 );12 13 // 调用代理方法14 proxy.createUser(new User("张三"));15 }16}1718class LoggingInvocationHandler implements InvocationHandler {19 private final Object target;20 21 public LoggingInvocationHandler(Object target) {22 this.target = target;23 }24 25 @Override26 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {27 System.out.println("方法执行前: " + method.getName());28 Object result = method.invoke(target, args);29 System.out.println("方法执行后: " + method.getName());30 return result;31 }32}JDK动态代理特点
- 要求目标类必须实现接口
- 基于Java反射机制
- 生成的代理类实现了相同的接口
- 通过InvocationHandler拦截方法调用
1public class CglibProxyExample {2 3 public static void main(String[] args) {4 Enhancer enhancer = new Enhancer();5 enhancer.setSuperclass(UserService.class);6 enhancer.setCallback(new LoggingMethodInterceptor());7 8 UserService proxy = (UserService) enhancer.create();9 proxy.createUser(new User("李四"));10 }11}1213class LoggingMethodInterceptor implements MethodInterceptor {14 15 @Override16 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {17 System.out.println("方法执行前: " + method.getName());18 Object result = proxy.invokeSuper(obj, args);19 System.out.println("方法执行后: " + method.getName());20 return result;21 }22}CGLIB代理特点
- 不要求目标类实现接口
- 基于ASM字节码操作框架
- 生成目标类的子类作为代理
- 通过覆盖父类方法实现代理
- 无法代理final类和final方法
2.2 代理选择机制
Spring AOP根据目标对象类型自动选择代理方式:
1public class ProxySelectionLogic {2 3 public static Object createProxy(Object target) {4 // 如果目标对象实现了接口,使用JDK动态代理5 if (target.getClass().getInterfaces().length > 0) {6 return createJdkProxy(target);7 } else {8 // 否则使用CGLIB代理9 return createCglibProxy(target);10 }11 }12 13 private static Object createJdkProxy(Object target) {14 // JDK动态代理实现15 return Proxy.newProxyInstance(16 target.getClass().getClassLoader(),17 target.getClass().getInterfaces(),18 new LoggingInvocationHandler(target)19 );20 }21 22 private static Object createCglibProxy(Object target) {23 // CGLIB代理实现24 Enhancer enhancer = new Enhancer();25 enhancer.setSuperclass(target.getClass());26 enhancer.setCallback(new LoggingMethodInterceptor());27 return enhancer.create();28 }29}3. 切点表达式
3.1 切点表达式语法
Spring AOP使用AspectJ切点表达式语言:
- 基础表达式
- 注解表达式
- 组合表达式
1@Aspect2@Component3public class BasicPointcutExamples {4 5 // 1. 方法执行切点 - 匹配service包下所有类的所有方法6 @Pointcut("execution(* com.example.service.*.*(..))")7 public void serviceMethods() {}8 9 // 2. 方法参数切点 - 匹配有两个参数(String和int)的方法10 @Pointcut("execution(* *.*(String, int))")11 public void methodsWithStringAndInt() {}12 13 // 3. 返回类型切点 - 匹配返回类型为void的方法14 @Pointcut("execution(void *.*(..))")15 public void voidMethods() {}16 17 // 4. 包切点 - 匹配service包及子包中的所有方法18 @Pointcut("execution(* com.example.service..*.*(..))")19 public void servicePackageAndSubpackages() {}20}执行表达式格式
execution(modifiers-pattern? return-type-pattern declaring-type-pattern? method-name-pattern(parameter-pattern) throws-pattern?)- modifiers-pattern:访问修饰符模式,如public、protected(可选)
- return-type-pattern:返回类型模式,如void、String、*(必选)
- declaring-type-pattern:声明类型模式,如com.example.service.*(可选)
- method-name-pattern:方法名模式,如get*、save、*(必选)
- parameter-pattern:参数模式,如(..)表示任意参数、(String)表示一个String参数(必选)
- throws-pattern:异常模式(可选)
1@Aspect2@Component3public class AnnotationPointcutExamples {4 5 // 1. 注解切点 - 匹配带有@Transactional注解的方法6 @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")7 public void transactionalMethods() {}8 9 // 2. 类型注解切点 - 匹配带有@Service注解的类中的所有方法10 @Pointcut("@within(org.springframework.stereotype.Service)")11 public void serviceClassMethods() {}12 13 // 3. 参数注解切点 - 匹配至少有一个参数带有@Valid注解的方法14 @Pointcut("@args(javax.validation.Valid)")15 public void methodsWithValidatedParams() {}16 17 // 4. 自定义注解示例18 @Pointcut("@annotation(com.example.annotation.LogExecutionTime)")19 public void logExecutionTimeMethods() {}20}1@Aspect2@Component3public class CombinedPointcutExamples {4 5 // 1. AND组合 - 匹配service包下且有@Transactional注解的方法6 @Pointcut("execution(* com.example.service.*.*(..)) && @annotation(org.springframework.transaction.annotation.Transactional)")7 public void serviceTransactionalMethods() {}8 9 // 2. OR组合 - 匹配任何控制器或服务方法10 @Pointcut("within(com.example.controller.*) || within(com.example.service.*)")11 public void controllerOrServiceMethods() {}12 13 // 3. NOT组合 - 匹配service包下非私有方法14 @Pointcut("execution(* com.example.service.*.*(..)) && !execution(private * *.*(..))")15 public void nonPrivateServiceMethods() {}16 17 // 4. 复杂组合 - 匹配有@Transactional注解的非getter、setter方法18 @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional) && !execution(* get*()) && !execution(* set*(*))")19 public void transactionalNonAccessorMethods() {}20}3.2 常用切点表达式
| 表达式 | 说明 | 示例 |
|---|---|---|
execution(* *.*(..)) | 所有方法执行 | 匹配所有方法 |
execution(* com.example.service.*.*(..)) | 指定包下的方法 | 匹配service包下所有方法 |
execution(* *.*(String, int)) | 指定参数的方法 | 匹配有两个参数的方法 |
@annotation(Transactional) | 注解方法 | 匹配有@Transactional注解的方法 |
within(com.example.service.*) | 指定包内的连接点 | 匹配service包内的所有连接点 |
this(com.example.service.UserService) | 代理对象类型 | 匹配代理对象为UserService的连接点 |
target(com.example.service.UserService) | 目标对象类型 | 匹配目标对象为UserService的连接点 |
args(String, ..) | 参数类型匹配 | 匹配第一个参数为String的方法 |
4. 通知类型
- 前置通知(@Before)
- 后置通知(@After)
- 环绕通知(@Around)
- 返回通知(@AfterReturning)
- 异常通知(@AfterThrowing)
1@Aspect2@Component3public class BeforeAdviceExample {4 5 @Before("execution(* com.example.service.*.*(..))")6 public void beforeAdvice(JoinPoint joinPoint) {7 String methodName = joinPoint.getSignature().getName();8 Object[] args = joinPoint.getArgs();9 10 System.out.println("方法执行前: " + methodName);11 System.out.println("参数: " + Arrays.toString(args));12 }13 14 @Before("@annotation(org.springframework.transaction.annotation.Transactional)")15 public void beforeTransactional(JoinPoint joinPoint) {16 System.out.println("事务方法执行前: " + joinPoint.getSignature().getName());17 }18}前置通知特点
- 在目标方法执行前执行
- 无法阻止方法执行(除非抛出异常)
- 可以访问方法参数,但不能修改返回值
- 适用场景:参数校验、权限检查、日志记录
1@Aspect2@Component3public class AfterAdviceExample {4 5 @After("execution(* com.example.service.*.*(..))")6 public void afterAdvice(JoinPoint joinPoint) {7 String methodName = joinPoint.getSignature().getName();8 System.out.println("方法执行后: " + methodName);9 }10 11 @After("execution(* com.example.repository.*.*(..))")12 public void afterRepositoryMethod(JoinPoint joinPoint) {13 String methodName = joinPoint.getSignature().getName();14 System.out.println("数据操作完成: " + methodName);15 }16}后置通知特点
- 在目标方法执行后执行(无论是否抛出异常)
- 类似于finally块
- 无法访问方法返回值
- 适用场景:资源清理、释放连接、记录方法执行时间
1@Aspect2@Component3public class AroundAdviceExample {4 5 @Around("execution(* com.example.service.*.*(..))")6 public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {7 String methodName = joinPoint.getSignature().getName();8 long startTime = System.currentTimeMillis();9 10 try {11 // 执行目标方法12 Object result = joinPoint.proceed();13 14 long endTime = System.currentTimeMillis();15 System.out.println("方法 " + methodName + " 执行时间: " + (endTime - startTime) + "ms");16 17 return result;18 } catch (Exception e) {19 System.out.println("方法 " + methodName + " 执行异常: " + e.getMessage());20 throw e;21 }22 }23 24 @Around("@annotation(com.example.annotation.LogExecutionTime)")25 public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {26 long start = System.currentTimeMillis();27 Object result = joinPoint.proceed();28 long executionTime = System.currentTimeMillis() - start;29 30 System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");31 return result;32 }33}环绕通知特点
- 可以在方法执行前后添加自定义行为
- 完全控制目标方法的执行(决定是否执行、何时执行)
- 可以修改方法的返回值或抛出的异常
- 需要手动调用joinPoint.proceed()执行目标方法
- 最强大但也最复杂的通知类型
- 适用场景:事务管理、性能监控、缓存、重试逻辑
1@Aspect2@Component3public class AfterReturningAdviceExample {4 5 @AfterReturning(6 pointcut = "execution(* com.example.service.*.*(..))",7 returning = "result"8 )9 public void afterReturningAdvice(JoinPoint joinPoint, Object result) {10 String methodName = joinPoint.getSignature().getName();11 System.out.println("方法 " + methodName + " 正常返回: " + result);12 }13 14 @AfterReturning(15 pointcut = "execution(java.util.List com.example.service.*.*(..))",16 returning = "resultList"17 )18 public void afterReturningList(JoinPoint joinPoint, List<?> resultList) {19 System.out.println("返回列表大小: " + resultList.size());20 }21}返回通知特点
- 在目标方法成功执行后执行
- 可以访问方法的返回值,但不能修改
- 仅在方法正常返回时执行(没有抛出异常)
- 适用场景:日志记录、统计分析、返回值处理
1@Aspect2@Component3public class AfterThrowingAdviceExample {4 5 @AfterThrowing(6 pointcut = "execution(* com.example.service.*.*(..))",7 throwing = "ex"8 )9 public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {10 String methodName = joinPoint.getSignature().getName();11 System.out.println("方法 " + methodName + " 抛出异常: " + ex.getMessage());12 13 // 记录异常日志14 // 发送异常通知15 // 清理资源16 }17 18 @AfterThrowing(19 pointcut = "execution(* com.example.repository.*.*(..))",20 throwing = "dataEx"21 )22 public void afterThrowingDataException(JoinPoint joinPoint, DataAccessException dataEx) {23 // 处理数据访问异常24 System.out.println("数据访问异常: " + dataEx.getMessage());25 }26}异常通知特点
- 在目标方法抛出异常时执行
- 可以访问抛出的异常对象
- 不能阻止异常传播(除非使用环绕通知)
- 适用场景:异常日志、错误通知、资源清理
4.1 通知类型对比
| 通知类型 | 注解 | 执行时机 | 能否控制方法执行 | 能否访问返回值 | 能否访问异常 | 适用场景 |
|---|---|---|---|---|---|---|
| 前置通知 | @Before | 方法执行前 | ❌ | ❌ | ❌ | 参数校验、权限检查 |
| 后置通知 | @After | 方法执行后(finally) | ❌ | ❌ | ❌ | 资源清理、日志记录 |
| 返回通知 | @AfterReturning | 方法正常返回后 | ❌ | ✅(只读) | ❌ | 结果处理、日志记录 |
| 异常通知 | @AfterThrowing | 方法抛出异常时 | ❌ | ❌ | ✅ | 异常处理、错误通知 |
| 环绕通知 | @Around | 方法前后都可 | ✅ | ✅(可修改) | ✅(可处理) | 事务管理、性能监控 |
5. 实际应用场景
5.1 日志切面
- 日志切面
- 自定义日志注解
1@Aspect2@Component3@Slf4j4public class LoggingAspect {5 6 @Around("@annotation(com.example.annotation.LogExecutionTime)")7 public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {8 String methodName = joinPoint.getSignature().getName();9 String className = joinPoint.getTarget().getClass().getSimpleName();10 11 log.info("开始执行方法: {}.{}", className, methodName);12 long startTime = System.currentTimeMillis();13 14 try {15 Object result = joinPoint.proceed();16 long endTime = System.currentTimeMillis();17 log.info("方法 {}.{} 执行完成,耗时: {}ms", className, methodName, endTime - startTime);18 return result;19 } catch (Exception e) {20 log.error("方法 {}.{} 执行异常: {}", className, methodName, e.getMessage());21 throw e;22 }23 }24 25 @Before("@annotation(com.example.annotation.LogParameters)")26 public void logParameters(JoinPoint joinPoint) {27 String methodName = joinPoint.getSignature().getName();28 Object[] args = joinPoint.getArgs();29 30 log.info("方法 {} 的参数: {}", methodName, Arrays.toString(args));31 }32}自定义日志注解示例
1@Target({ElementType.METHOD})2@Retention(RetentionPolicy.RUNTIME)3public @interface LogExecutionTime {4 String value() default "";5}67@Target({ElementType.METHOD})8@Retention(RetentionPolicy.RUNTIME)9public @interface LogParameters {10 String value() default "";11}日志切面功能
- 方法调用记录:记录方法的调用信息,包括方法名、参数等
- 执行时间统计:记录方法的执行时长,用于性能分析
- 异常捕获:记录方法执行过程中的异常信息
- 结果跟踪:记录方法的返回值
- 调用链跟踪:跟踪完整的方法调用链
5.2 缓存切面
- 缓存切面
- 缓存使用示例
1@Aspect2@Component3public class CacheAspect {4 5 @Autowired6 private RedisTemplate<String, Object> redisTemplate;7 8 @Around("@annotation(com.example.annotation.Cacheable)")9 public Object cache(ProceedingJoinPoint joinPoint) throws Throwable {10 String key = generateCacheKey(joinPoint);11 12 // 尝试从缓存获取13 Object cachedValue = redisTemplate.opsForValue().get(key);14 if (cachedValue != null) {15 return cachedValue;16 }17 18 // 执行方法并缓存结果19 Object result = joinPoint.proceed();20 redisTemplate.opsForValue().set(key, result, Duration.ofMinutes(30));21 22 return result;23 }24 25 @AfterReturning("@annotation(com.example.annotation.CacheEvict)")26 public void evictCache(JoinPoint joinPoint) {27 String key = generateCacheKey(joinPoint);28 redisTemplate.delete(key);29 }30 31 private String generateCacheKey(JoinPoint joinPoint) {32 String methodName = joinPoint.getSignature().getName();33 Object[] args = joinPoint.getArgs();34 return methodName + ":" + Arrays.toString(args);35 }36}1@Service2public class UserService {3 4 @Autowired5 private UserRepository userRepository;6 7 @Cacheable8 public User getUserById(Long id) {9 // 这个方法的结果会被缓存10 return userRepository.findById(id).orElse(null);11 }12 13 @CacheEvict14 public void updateUser(User user) {15 // 更新用户后,相关缓存会被清除16 userRepository.save(user);17 }18}缓存切面优势
- 减少数据库负载:避免重复查询相同数据
- 提高响应速度:直接从缓存返回结果
- 透明实现:业务代码无感知缓存操作
- 集中管理:缓存策略集中配置
5.3 事务切面
- 事务切面
- 事务使用示例
1@Aspect2@Component3public class TransactionAspect {4 5 @Autowired6 private PlatformTransactionManager transactionManager;7 8 @Around("@annotation(com.example.annotation.Transactional)")9 public Object handleTransaction(ProceedingJoinPoint joinPoint) throws Throwable {10 TransactionStatus status = null;11 12 try {13 // 开启事务14 DefaultTransactionDefinition def = new DefaultTransactionDefinition();15 status = transactionManager.getTransaction(def);16 17 // 执行目标方法18 Object result = joinPoint.proceed();19 20 // 提交事务21 transactionManager.commit(status);22 23 return result;24 } catch (Exception e) {25 // 回滚事务26 if (status != null) {27 transactionManager.rollback(status);28 }29 throw e;30 }31 }32}事务切面功能
- 自动事务管理:无需手动开启/提交/回滚事务
- 声明式事务:使用注解即可启用事务
- 事务传播:支持不同的事务传播行为
- 事务隔离:支持不同的事务隔离级别
- 异常处理:自动根据异常类型回滚事务
1@Service2public class OrderService {3 4 @Autowired5 private OrderRepository orderRepository;6 7 @Autowired8 private PaymentService paymentService;9 10 @Transactional11 public Order createOrder(Order order) {12 // 保存订单13 Order savedOrder = orderRepository.save(order);14 15 // 处理支付,如果支付失败,整个事务会回滚16 paymentService.processPayment(order.getPayment());17 18 return savedOrder;19 }20}5.4 权限切面
- 权限切面
- 权限使用示例
1@Aspect2@Component3public class SecurityAspect {4 5 @Autowired6 private SecurityService securityService;7 8 @Before("@annotation(com.example.annotation.RequiresPermission)")9 public void checkPermission(JoinPoint joinPoint) {10 MethodSignature signature = (MethodSignature) joinPoint.getSignature();11 RequiresPermission annotation = signature.getMethod().getAnnotation(RequiresPermission.class);12 String permission = annotation.value();13 14 if (!securityService.hasPermission(permission)) {15 throw new AccessDeniedException("权限不足: " + permission);16 }17 }18 19 @Before("@annotation(com.example.annotation.RequiresRole)")20 public void checkRole(JoinPoint joinPoint) {21 MethodSignature signature = (MethodSignature) joinPoint.getSignature();22 RequiresRole annotation = signature.getMethod().getAnnotation(RequiresRole.class);23 String role = annotation.value();24 25 if (!securityService.hasRole(role)) {26 throw new AccessDeniedException("角色不足: " + role);27 }28 }29}1@RestController2@RequestMapping("/api/admin")3public class AdminController {4 5 @RequiresRole("ADMIN")6 @GetMapping("/users")7 public List<User> getAllUsers() {8 // 只有ADMIN角色才能访问9 return userService.findAll();10 }11 12 @RequiresPermission("user:delete")13 @DeleteMapping("/users/{id}")14 public void deleteUser(@PathVariable Long id) {15 // 只有拥有user:delete权限才能删除用户16 userService.deleteById(id);17 }18}权限切面优势
- 声明式安全控制:通过注解声明权限需求
- 细粒度权限控制:精确到方法级别
- 集中管理:安全逻辑与业务逻辑分离
- 可扩展性:易于添加新的权限类型
- 一致性:统一的权限控制机制
6. 性能监控切面
6.1 方法执行时间监控
1@Aspect2@Component3public class PerformanceMonitorAspect {4 5 private final MeterRegistry meterRegistry;6 7 public PerformanceMonitorAspect(MeterRegistry meterRegistry) {8 this.meterRegistry = meterRegistry;9 }10 11 @Around("execution(* com.example.service.*.*(..))")12 public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {13 String methodName = joinPoint.getSignature().getName();14 String className = joinPoint.getTarget().getClass().getSimpleName();15 String metricName = className + "." + methodName;16 17 Timer.Sample sample = Timer.start(meterRegistry);18 19 try {20 Object result = joinPoint.proceed();21 sample.stop(Timer.builder("method.execution.time")22 .tag("class", className)23 .tag("method", methodName)24 .tag("status", "success")25 .register(meterRegistry));26 return result;27 } catch (Exception e) {28 sample.stop(Timer.builder("method.execution.time")29 .tag("class", className)30 .tag("method", methodName)31 .tag("status", "error")32 .register(meterRegistry));33 34 // 记录异常计数35 meterRegistry.counter("method.execution.errors",36 "class", className,37 "method", methodName,38 "exception", e.getClass().getSimpleName()).increment();39 40 throw e;41 }42 }43}7. 面试题精选
7.1 基础概念题
- 什么是AOP
- AOP实现原理
- 切点表达式
Q: 什么是AOP?它的核心概念有哪些?
A: AOP(面向切面编程)是一种编程范式,用于将横切关注点从主业务逻辑中分离出来。核心概念包括:
- Aspect(切面):横切关注点的模块化
- Join Point(连接点):程序执行过程中的某个特定点
- Pointcut(切点):匹配连接点的表达式
- Advice(通知):在切点处要执行的代码
- Target Object(目标对象):被代理的对象
- Proxy(代理):AOP框架创建的对象
- Weaving(织入):将切面应用到目标对象的过程
Q: Spring AOP的实现原理是什么?
A: Spring AOP基于动态代理实现:
- JDK动态代理:适用于实现了接口的目标对象
- CGLIB代理:适用于没有实现接口的目标对象
- Spring根据目标对象类型自动选择代理方式
- 通过代理对象拦截方法调用,在方法执行前后插入横切逻辑
实现流程:
- 解析切面及切点表达式
- 创建目标对象的代理
- 调用代理对象的方法时,通过拦截器链执行通知和目标方法
Q: 常用的切点表达式有哪些?
A: 常用的切点表达式包括:
execution(* com.example.service..(..)):匹配service包中的所有方法@annotation(org.springframework.transaction.annotation.Transactional):匹配带有@Transactional注解的方法within(com.example.service.*):匹配service包中所有类的所有方法this(com.example.service.UserService):匹配代理对象为UserService的连接点target(com.example.service.UserService):匹配目标对象为UserService的连接点args(String, int):匹配接受String和int参数的方法
这些表达式可以通过&&(and)、||(or)、!(not)组合使用,形成更复杂的匹配规则。
7.2 实践题
- 日志切面
- AOP通知类型
Q: 如何实现一个日志切面?
A: 实现日志切面的步骤:
- 创建切面类并使用
@Aspect注解 - 定义切点表达式
- 使用通知注解(如
@Around)实现日志逻辑 - 在切面中记录方法执行时间、参数、返回值等信息
核心代码示例:
1@Aspect2@Component3public class LoggingAspect {4 @Around("execution(* com.example.service.*.*(..))")5 public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {6 String methodName = joinPoint.getSignature().getName();7 log.info("开始执行: {}", methodName);8 long start = System.currentTimeMillis();9 try {10 Object result = joinPoint.proceed();11 log.info("执行完成: {}, 耗时: {}ms", methodName, System.currentTimeMillis() - start);12 return result;13 } catch (Exception e) {14 log.error("执行异常: {}, 异常: {}", methodName, e.getMessage());15 throw e;16 }17 }18}Q: AOP有哪些通知类型?
A: Spring AOP提供五种通知类型:
- @Before:前置通知,在方法执行前执行
- @After:后置通知,在方法执行后执行
- @Around:环绕通知,可以控制方法执行
- @AfterReturning:返回通知,在方法正常返回后执行
- @AfterThrowing:异常通知,在方法抛出异常后执行
通知类型选择原则:
- 需要在方法执行前后都添加逻辑,选择环绕通知
- 只需要在方法执行前添加逻辑,选择前置通知
- 只需要在方法执行后添加逻辑,选择后置通知或返回通知
- 需要处理方法执行异常,选择异常通知或环绕通知
- 优先使用最简单的通知类型,避免使用过于复杂的环绕通知
7.3 高级题
- Spring AOP vs AspectJ
- AOP自调用问题
Q: Spring AOP和AspectJ有什么区别?
A: 主要区别包括:
| 比较项 | Spring AOP | AspectJ |
|---|---|---|
| 实现方式 | 基于动态代理 | 基于字节码增强 |
| 织入时机 | 运行时织入 | 编译时、编译后、加载时织入 |
| 性能 | 较慢 | 更好 |
| 功能 | 有限 | 更强大 |
| 切点表达式 | 支持部分AspectJ表达式 | 支持完整的切点表达式语言 |
| 连接点类型 | 仅方法执行 | 方法调用、字段访问、构造器调用等 |
| 易用性 | 简单易用 | 更复杂但功能强大 |
| 应用场景 | 简单应用 | 复杂应用 |
Q: 如何解决AOP中的自调用问题?
A: 自调用问题是指同一个类中的方法调用不会经过代理。解决方案:
1. 使用AopContext
1@Service2public class UserService {3 public void createUser(User user) {4 userRepository.save(user);5 // 使用AopContext获取代理对象6 ((UserService) AopContext.currentProxy()).sendEmail(user);7 }8 9 @Transactional10 public void sendEmail(User user) {11 // 发送邮件的事务方法12 }13}注意:需要在配置中启用@EnableAspectJAutoProxy(exposeProxy = true)
2. 注入自身代理
1@Service2public class UserService {3 @Autowired4 private UserService self; // 注入的是代理对象5 6 public void createUser(User user) {7 userRepository.save(user);8 self.sendEmail(user); // 通过代理调用9 }10 11 @Transactional12 public void sendEmail(User user) {13 // 发送邮件的事务方法14 }15}3. 方法抽取:将方法移到另一个类中
4. 使用AspectJ:使用AspectJ的编译时或加载时织入,可以解决自调用问题
- 理解核心概念:掌握Aspect、Pointcut、Advice等概念
- 熟悉切点表达式:学会编写各种切点表达式
- 掌握通知类型:了解五种通知的使用场景
- 学会实际应用:掌握日志、缓存、事务等切面实现
- 了解性能优化:学会性能监控和优化技巧
通过本章的学习,你应该已经掌握了Spring AOP的核心概念、实现原理和实际应用。AOP是Spring框架的重要特性,掌握AOP可以帮助你编写更加模块化、可维护的代码。在实际项目中,合理使用AOP可以大大减少重复代码,提高开发效率。
8. 实用示例
8.1 性能日志记录器
1@Aspect2@Component3@Slf4j4public class LoggingAspect {5 @Around("execution(* com.example.service.*.*(..))")6 public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {7 String methodName = joinPoint.getSignature().getName();8 log.info("开始执行: {}", methodName);9 long start = System.currentTimeMillis();10 try {11 Object result = joinPoint.proceed();12 log.info("执行完成: {}, 耗时: {}ms", methodName, System.currentTimeMillis() - start);13 return result;14 } catch (Exception e) {15 log.error("执行异常: {}, 异常: {}", methodName, e.getMessage());16 throw e;17 }18 }19}
评论