本文目录导读:

在Java中,实现环绕通知最主流的做法是使用Spring AOP的@Around注解,环绕通知可以在目标方法执行前后添加自定义逻辑,甚至可以完全控制目标方法的执行。
基础环境搭建
确保项目引入Spring AOP依赖:
Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
启用AOP
在配置类上添加@EnableAspectJAutoProxy注解:
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
}
创建环绕通知
示例1:简单的性能监控切面
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 PerformanceAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
// 前置逻辑:记录开始时间
long startTime = System.currentTimeMillis();
System.out.println("开始执行方法: " + joinPoint.getSignature().getName());
try {
// 执行目标方法
Object result = joinPoint.proceed();
return result;
} finally {
// 后置逻辑:计算执行时间
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
System.out.println("方法 " + joinPoint.getSignature().getName() +
" 执行耗时: " + duration + "ms");
}
}
}
示例2:日志记录切面
@Aspect
@Component
public class LoggingAspect {
@Around("execution(* com.example.controller.*.*(..))")
public Object logMethodCall(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取方法参数
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("调用方法: " + methodName);
System.out.println("参数: " + Arrays.toString(args));
Object result = null;
try {
// 执行目标方法
result = joinPoint.proceed();
System.out.println("方法返回: " + result);
return result;
} catch (Exception e) {
System.out.println("方法异常: " + e.getMessage());
throw e;
} finally {
System.out.println("方法执行完毕: " + methodName);
}
}
}
示例3:权限验证切面
@Aspect
@Component
public class SecurityAspect {
@Around("@annotation(com.example.annotation.RequireAuth)")
public Object checkAuthentication(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取当前用户信息(假设从ThreadLocal获取)
User currentUser = SecurityContext.getCurrentUser();
if (currentUser == null) {
throw new SecurityException("用户未登录");
}
// 获取注解信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
RequireAuth annotation = signature.getMethod().getAnnotation(RequireAuth.class);
String requiredRole = annotation.role();
// 检查权限
if (!currentUser.hasRole(requiredRole)) {
throw new SecurityException("权限不足");
}
// 权限验证通过,执行目标方法
return joinPoint.proceed();
}
}
目标业务类
@Service
public class UserService {
public User findUserById(Long id) {
System.out.println("查询用户ID: " + id);
// 模拟耗时操作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new User(id, "张三");
}
}
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
@RequireAuth(role = "ADMIN")
public User getUser(@PathVariable Long id) {
return new User(id, "张三");
}
}
自定义注解
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 RequireAuth {
String role() default "USER";
}
测试类
@SpringBootTest
@RunWith(SpringRunner.class)
public class AopTest {
@Autowired
private UserService userService;
@Test
public void testAroundAdvice() {
userService.findUserById(1L);
}
}
关键知识点
ProceedingJoinPoint 常用方法
| 方法 | 说明 |
|---|---|
proceed() |
执行目标方法 |
proceed(Object[] args) |
用指定的参数执行目标方法 |
getArgs() |
获取方法参数数组 |
getSignature() |
获取方法签名 |
getTarget() |
获取目标对象 |
getThis() |
获取代理对象 |
环绕通知执行流程
@Around("pointcut")
public Object around(ProceedingJoinPoint pjp) {
// 1. 前置处理
// ...
try {
// 2. 执行目标方法
Object result = pjp.proceed();
// 3. 后置处理(方法正常返回)
// ...
return result;
} catch (Exception e) {
// 4. 异常处理
// ...
throw e;
}
}
注意事项
- 必须调用proceed():如果环绕通知不调用
proceed(),目标方法将不会执行 - 返回结果处理:环绕通知必须返回目标方法的执行结果,否则调用方将收到null
- 异常传播:异常应该继续向上抛出,除非你想改变异常类型
- 线程安全:确保环绕通知中的共享资源访问是线程安全的
环绕通知是最强大的通知类型,因为它可以完全控制目标方法的执行过程,适合用于事务管理、日志记录、性能监控、安全控制等场景。