背景现代轻量级Spring框架的应用系统中可能在多个地方要求日志记录如果某个业务需要记录日志都要写日志记录信息的话那么代码侵入业务逻辑太强有没有什么办法能够将日志和业务逻辑分开我们只需要配置就可以记录到比如方法调用结果返回等而我们只需要负责实现业务逻辑这个时候我们就可以利用AOP切面用于记录日志用注解作为切入点将日志记录对业务代码的侵入分隔开更好的维护程序导入依赖!-- lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version1.18.30/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency !-- Aop切面 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-aop/artifactId /dependency首先我们定义一个注解用于作为AOP代理的切入点import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * TODO * 作用定义一个注解用于记录日志 * 作用域作用域方法 * 作用时间运行时有效 * value日志表述 * saveParams是否记录参数 * saveResult是否记录返回值默认关闭防止数据过大 */ Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface Loggable { String value() default ; boolean saveParams() default true; boolean saveResult() default false; }其次我们编写AOP切面类在方法被访问的时候记录日志import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; 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; /** * TODO * 编写AOP切面类使用Around(环绕溶脂)老包裹目标方法从而可以计算耗时、捕获参数和返回值 */ Aspect Component Slf4j public class LogAspect { private final ObjectMapper objectMapper new ObjectMapper(); //设置切入点匹配带有loggable注解的方法 Around(annotation(loggable)) public Object logAround(ProceedingJoinPoint proceedingJoinPoint,Loggable loggable) throws Throwable { //获取上下文中的信息可选 HttpServletRequest request ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); //获取请求的URI从HTTP请求中提取 例如 /api/user/list String requestURI request.getRequestURI(); //获取请求的方法 例如 POST、GET、PUT、DELETE String method request.getMethod(); //获取方法签名和参数 MethodSignature signature (MethodSignature)proceedingJoinPoint.getSignature(); //获取目标方法 Method method1 signature.getMethod(); //从连接点中提取被拦截方法调用时传入的所有参数返回一个对象数组。 Object[] args proceedingJoinPoint.getArgs(); // 3. 记录开始日志 log.info( 开始执行: {} - {}, 描述: {}, requestURI, method, loggable.value()); if (loggable.saveParams()) { log.info( 请求参数: {}, toJson(args)); } // 4. 计算耗时并执行目标方法 long startTime System.currentTimeMillis(); Object result null; try { result proceedingJoinPoint.proceed(); // 执行目标方法 return result; } catch (Throwable e) { log.error(❌ 执行异常: {} - {}, requestURI, e.getMessage(), e); throw e; // 抛出异常让全局异常处理器处理 } finally { long endTime System.currentTimeMillis(); long duration endTime - startTime; // 5. 记录结束日志 log.info(✅ 执行结束: {} - {}, 耗时: {}ms, requestURI, method, duration); if (loggable.saveResult() result ! null) { log.info( 返回结果: {}, toJson(result)); } } } // 简单的 JSON 序列化工具方法 private String toJson(Object obj) { try { return objectMapper.writeValueAsString(obj); } catch (Exception e) { return obj.toString(); } } }测试程序import com.lym.demo.demos.util.logs.Loggable; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; /** * author a hrefmailto:chenxilzx1gmail.comtheonefx/a */ Controller public class BasicController { // http://127.0.0.1:8080/hello?namelisi Loggable(value hello接口测试,saveParams true,saveResult true) RequestMapping(/hello) ResponseBody public String hello(RequestParam(name name, defaultValue unknown user) String name) { return Hello name; } // http://127.0.0.1:8080/user Loggable(value 获取用户信息测试,saveParams true,saveResult true) RequestMapping(/user) ResponseBody public User user() { User user new User(); user.setName(theonefx); user.setAge(666); return user; } // http://127.0.0.1:8080/save_user?namenewNameage11 Loggable(value 保存用户信息测试,saveParams true,saveResult true) RequestMapping(/save_user) ResponseBody public String saveUser(User u) { return user will save: name u.getName() , age u.getAge(); } // http://127.0.0.1:8080/html RequestMapping(/html) public String html() { return index.html; } ModelAttribute public void parseUser(RequestParam(name name, defaultValue unknown user) String name , RequestParam(name age, defaultValue 12) Integer age, User user) { user.setName(zhangsan); user.setAge(18); } }测试结果后续可将日志数据持久化到数据库