下面我将展示如何使用Spring AOP来实现一个@Log
注解,用于记录Controller方法的请求参数和执行时间。
1. 创建自定义注解
首先创建一个@Log
注解:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
String value() default "";
boolean printArgs() default true; // 是否打印参数
boolean printTime() default true; // 是否打印执行时间
}
2. 创建AOP切面类
创建一个切面类来处理@Log
注解:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
@Aspect
@Component
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
// 定义切点:所有带有@Log注解的方法
@Pointcut("@annotation(com.yourpackage.annotation.Log)")
public void logPointCut() {}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取注解
Log logAnnotation = method.getAnnotation(Log.class);
String methodName = logAnnotation.value().isEmpty()
? method.getDeclaringClass().getSimpleName() + "." + method.getName()
: logAnnotation.value();
// 获取请求信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes != null ? attributes.getRequest() : null;
// 记录请求信息
if (request != null && logAnnotation.printArgs()) {
logger.info("请求开始 => 方法名: {}, URL: {}, HTTP方法: {}, IP: {}, 参数: {}",
methodName,
request.getRequestURL().toString(),
request.getMethod(),
request.getRemoteAddr(),
Arrays.toString(joinPoint.getArgs()));
} else if (logAnnotation.printArgs()) {
logger.info("请求开始 => 方法名: {}, 参数: {}",
methodName,
Arrays.toString(joinPoint.getArgs()));
} else {
logger.info("请求开始 => 方法名: {}", methodName);
}
// 记录执行时间
long startTime = System.currentTimeMillis();
Object result;
try {
result = joinPoint.proceed();
} finally {
long endTime = System.currentTimeMillis();
if (logAnnotation.printTime()) {
logger.info("请求结束 => 方法名: {}, 执行耗时: {}ms",
methodName,
endTime - startTime);
}
}
return result;
}
}
3. 在Controller中使用注解
在Controller方法上使用@Log
注解:
import com.yourpackage.annotation.Log;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class TestController {
@Log("测试方法1")
@GetMapping("/test1")
public String test1(@RequestParam String name) {
return "Hello " + name;
}
@Log(printArgs = false) // 不打印参数
@PostMapping("/test2")
public User test2(@RequestBody User user) {
return user;
}
@Log(printTime = false) // 不打印执行时间
@GetMapping("/test3")
public String test3() {
return "Simple test";
}
}
4. 确保Spring Boot配置正确
确保你的Spring Boot应用已经启用了AOP支持。在Spring Boot中,AOP默认是启用的,但如果你需要确认,可以在主类上添加@EnableAspectJAutoProxy
:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@EnableAspectJAutoProxy
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
5. 日志输出示例
当请求到达被注解的方法时,日志输出类似这样:
请求开始 => 方法名: 测试方法1, URL: http://localhost:8080/api/test1, HTTP方法: GET, IP: 127.0.0.1, 参数: [John]
请求结束 => 方法名: 测试方法1, 执行耗时: 12ms
高级定制
可以根据需要扩展这个切面:
- 记录返回值:在切面中添加返回值的逻辑
- 异常处理:添加
@AfterThrowing
建议来记录异常情况 - 自定义日志格式:修改日志输出格式
- 敏感信息过滤:对参数中的敏感信息进行过滤
- 异步方法支持:添加对异步方法的支持
这个实现提供了灵活的控制,通过注解参数可以决定是否打印参数和执行时间,还可以自定义方法名,非常适合生产环境使用。