在 Java 企业级开发中,Spring AOP(面向切面编程)是一个极其重要的模块,它允许开发者在不修改核心业务逻辑代码的情况下,为应用程序添加横切关注点(cross-cutting concerns)。而 @Around 注解则是 Spring AOP 中最强大、最灵活的通知类型,它能够完全控制目标方法的执行流程。
在深入 @Around 之前,我们需要明确几个 AOP 核心概念:
@Aspect 注解的类。@Around 通知之所以强大,是因为它:
ProceedingJoinPoint.proceed())ProceedingJoinPoint.getArgs() 获取并修改)这种全方位的控制能力使得 @Around 成为实现复杂横切逻辑的首选。
一个标准的 @Around 通知定义如下:
java复制@Aspect
@Component
public class MyAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
// 前置逻辑
Object result = pjp.proceed(); // 执行目标方法
// 后置逻辑
return result;
}
}
关键点说明:
@Aspect 标记这是一个切面类@Component 让 Spring 管理这个 Bean@Around 定义切入点表达式ProceedingJoinPoint 参数提供了对目标方法的访问切入点表达式决定了哪些方法会被通知拦截。Spring 支持多种表达式写法:
execution:最常用的表达式
execution(public * *(..)):所有公共方法execution(* set*(..)):所有以 set 开头的方法execution(* com.xyz.service.*.*(..)):指定包下所有类的所有方法execution(* com.xyz.service..*.*(..)):指定包及其子包下所有方法within:限定类型
within(com.xyz.service.*):service 包下所有类within(com.xyz.service..*):service 包及其子包下所有类this 和 target:基于代理对象的限制
args:基于参数类型的限制
@annotation:基于方法注解的限制
@annotation(com.xyz.security.RequiresRole):拦截带有 @RequiresRole 注解的方法ProceedingJoinPoint 接口提供了丰富的方法来操作目标方法:
java复制public interface ProceedingJoinPoint extends JoinPoint {
Object proceed() throws Throwable;
Object proceed(Object[] args) throws Throwable;
}
public interface JoinPoint {
Object getThis(); // 返回代理对象
Object getTarget(); // 返回目标对象
Object[] getArgs(); // 返回方法参数
Signature getSignature(); // 返回方法签名
SourceLocation getSourceLocation(); // 返回源位置
String toString(); // 连接点信息
String toShortString(); // 简略信息
String toLongString(); // 详细信息
JoinPoint.StaticPart getStaticPart(); // 静态部分
}
实际开发中最常用的方法:
proceed():执行目标方法getArgs():获取方法参数(可用于参数校验或修改)getSignature():获取方法签名信息getTarget():获取目标对象(可用于获取目标类信息)一个完整的性能监控实现示例:
java复制@Around("execution(* com.example.service..*(..))")
public Object monitorPerformance(ProceedingJoinPoint pjp) throws Throwable {
String className = pjp.getTarget().getClass().getSimpleName();
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
// 记录方法开始时间
long startTime = System.currentTimeMillis();
logger.info("开始执行 {}.{},参数:{}", className, methodName, Arrays.toString(args));
try {
Object result = pjp.proceed();
// 记录方法结束时间
long endTime = System.currentTimeMillis();
logger.info("完成执行 {}.{},耗时:{}ms,结果:{}",
className, methodName, (endTime - startTime), result);
return result;
} catch (Exception e) {
logger.error("执行 {}.{} 时发生异常:{}",
className, methodName, e.getMessage(), e);
throw e;
}
}
注意:在实际生产环境中,建议使用 SLF4J 等专业日志框架而非 System.out,并考虑异步记录日志以避免性能影响。
基于自定义注解的权限校验方案:
java复制@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiresPermission {
String value(); // 需要的权限标识
}
java复制@Around("@annotation(requiresPermission)")
public Object checkPermission(ProceedingJoinPoint pjp, RequiresPermission requiresPermission) throws Throwable {
String requiredPermission = requiresPermission.value();
// 从请求上下文中获取当前用户
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.currentRequestAttributes()).getRequest();
User currentUser = (User) request.getSession().getAttribute("currentUser");
if (currentUser == null || !currentUser.hasPermission(requiredPermission)) {
throw new AccessDeniedException("没有访问权限:" + requiredPermission);
}
return pjp.proceed();
}
java复制@RequiresPermission("user:delete")
public void deleteUser(Long userId) {
// 业务逻辑
}
一个简单的缓存切面实现:
java复制@Around("@annotation(cacheable)")
public Object handleCache(ProceedingJoinPoint pjp, Cacheable cacheable) throws Throwable {
String cacheKey = generateCacheKey(pjp);
Object cachedValue = cacheManager.get(cacheKey);
if (cachedValue != null) {
return cachedValue;
}
Object result = pjp.proceed();
cacheManager.put(cacheKey, result, cacheable.expire(), TimeUnit.SECONDS);
return result;
}
private String generateCacheKey(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Object[] args = pjp.getArgs();
StringBuilder sb = new StringBuilder();
sb.append(method.getDeclaringClass().getName())
.append(".")
.append(method.getName());
for (Object arg : args) {
sb.append("_").append(arg != null ? arg.toString() : "null");
}
return sb.toString();
}
虽然 Spring 已经提供了 @Transactional,但我们可以通过 @Around 实现更细粒度的事务控制:
java复制@Around("@annotation(customTransactional)")
public Object manageTransaction(ProceedingJoinPoint pjp, CustomTransactional customTransactional) throws Throwable {
TransactionDefinition definition = new DefaultTransactionDefinition();
definition.setIsolationLevel(customTransactional.isolation().value());
definition.setPropagationBehavior(customTransactional.propagation().value());
definition.setTimeout(customTransactional.timeout());
TransactionStatus status = transactionManager.getTransaction(definition);
try {
Object result = pjp.proceed();
transactionManager.commit(status);
return result;
} catch (Exception e) {
if (shouldRollbackOn(e, customTransactional.rollbackFor())) {
transactionManager.rollback(status);
} else {
transactionManager.commit(status);
}
throw e;
}
}
private boolean shouldRollbackOn(Throwable ex, Class<? extends Throwable>[] rollbackFor) {
// 实现回滚规则判断逻辑
}
虽然 AOP 非常强大,但不合理的使用会导致性能问题:
切入点表达式优化:
execution(* *(..)))within() 限定包范围代理机制选择:
通知逻辑优化:
单一职责原则:
明确边界:
文档化:
自调用问题:
异常处理不当:
java复制try {
return pjp.proceed();
} catch (Exception e) {
// 处理异常
throw e; // 必须重新抛出
}
循环依赖:
让我们看一个完整的实际案例 - 构建一个 API 接口监控系统,记录接口调用情况并统计性能指标。
java复制@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiMonitor {
String name() default "";
boolean recordParams() default true;
boolean recordResult() default false;
}
java复制@Aspect
@Component
public class ApiMonitorAspect {
@Autowired
private ApiStatsRepository statsRepository;
@Around("@annotation(monitor)")
public Object monitorApi(ProceedingJoinPoint pjp, ApiMonitor monitor) throws Throwable {
String apiName = monitor.name().isEmpty()
? pjp.getSignature().toShortString()
: monitor.name();
ApiStats stats = new ApiStats();
stats.setApiName(apiName);
stats.setStartTime(System.currentTimeMillis());
if (monitor.recordParams()) {
stats.setParams(Arrays.toString(pjp.getArgs()));
}
try {
Object result = pjp.proceed();
stats.setSuccess(true);
if (monitor.recordResult()) {
stats.setResult(result != null ? result.toString() : "null");
}
return result;
} catch (Exception e) {
stats.setSuccess(false);
stats.setErrorMsg(e.getMessage());
throw e;
} finally {
stats.setEndTime(System.currentTimeMillis());
statsRepository.save(stats);
}
}
}
java复制@RestController
@RequestMapping("/api/users")
public class UserController {
@ApiMonitor(name = "获取用户列表", recordParams = false)
@GetMapping
public List<User> listUsers() {
// 业务逻辑
}
@ApiMonitor(name = "创建用户", recordParams = true, recordResult = true)
@PostMapping
public User createUser(@RequestBody User user) {
// 业务逻辑
}
}
可以定期从 ApiStatsRepository 中读取数据,生成各种统计报表:
测试切面逻辑同样重要:
java复制@RunWith(SpringRunner.class)
@SpringBootTest
public class LoggingAspectTest {
@Autowired
private TestService testService;
@Autowired
private LoggingAspect loggingAspect;
@Test
public void testAroundAdvice() {
testService.doSomething("test");
verify(loggingAspect, times(1))
.logMethodExecution(any(ProceedingJoinPoint.class));
}
}
确认切面生效:
@Component)@EnableAspectJAutoProxy 已启用日志输出:
代理类型检查:
java复制System.out.println(target.getClass()); // 输出代理类名
使用 Spring Boot Actuator:
/beans 端点查看已注册的切面理解 @Around 与其他通知类型的区别很重要:
| 通知类型 | 执行时机 | 能否阻止方法执行 | 能否修改返回值 | 能否处理异常 |
|---|---|---|---|---|
| @Before | 方法执行前 | 否 | 否 | 否 |
| @After | 方法执行后 | 否 | 否 | 否 |
| @AfterReturning | 方法成功返回后 | 否 | 能 | 否 |
| @AfterThrowing | 方法抛出异常后 | 否 | 否 | 能 |
| @Around | 方法执行前后 | 能 | 能 | 能 |
何时选择 @Around:
何时选择其他通知:
虽然本文主要讨论 Spring AOP,但了解它与 AspectJ 的区别也很重要:
| 特性 | Spring AOP | AspectJ |
|---|---|---|
| 实现方式 | 运行时动态代理 | 编译时/加载时织入 |
| 连接点支持 | 仅方法执行 | 全部连接点(构造器、字段访问等) |
| 性能 | 较好 | 最佳 |
| 复杂度 | 简单 | 复杂 |
| 依赖 | 仅需 Spring | 需要 AspectJ 编译器 |
何时选择 AspectJ:
Spring AOP 已经能满足大多数企业应用需求,是更轻量级的选择。
可能原因:
@Component)@EnableAspectJAutoProxy)java复制MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
java复制Object[] args = pjp.getArgs();
// 修改 args
Object result = pjp.proceed(args);
解决方案:
使用 Spring Profile:
java复制@Aspect
@Component
@Profile("prod")
public class ProductionMonitoringAspect {
// 生产环境特定的监控逻辑
}
@Aspect
@Component
@Profile("dev")
public class DevelopmentMonitoringAspect {
// 开发环境特定的监控逻辑
}
经过多年 Spring 项目实践,我发现 @Around 通知是最常用也最强大的 AOP 工具。以下是一些个人经验:
保持切面简单:切面逻辑应该尽可能简单,复杂逻辑应该放在服务类中。
明确命名:切面类和方法名应该清晰表达其功能,如 LoggingAspect、PerformanceMonitoringAspect。
谨慎使用:不是所有横切关注点都适合用 AOP 实现,有时过滤器或拦截器可能是更好的选择。
文档化切面:在团队中明确记录每个切面的作用和影响范围,避免隐式行为。
性能测试:添加重要切面前后进行性能测试,确保不会引入显著性能开销。
异常处理:在 @Around 通知中正确处理异常,避免"吞掉"重要异常。
测试覆盖:为切面逻辑编写单元测试,确保其行为符合预期。
监控切面本身:对于关键切面,考虑添加监控机制来确保它们正常工作。
在实际项目中,我通常会创建以下几个常用切面:
记住,AOP 是一种强大的工具,但像所有工具一样,需要合理使用才能发挥最大价值。过度使用 AOP 会导致代码难以理解和维护,而适度使用则可以显著提高代码的模块化和可维护性。