AOP实现日志打印方法入参出参

本文介绍了AOP(面向切面编程)的基本术语,如通知、连接点、切点、切面、引入和织入,并详细讲解了如何在Java中实现日志打印,包括自定义接口和定义切面,以记录方法的入参和出参。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

AOP术语

1、通知(Advise)
通知 执行顺序
前置通知(Before) 在目标方法被调用之前调用通知功能
后置通知(After) 在目标方法完成之后调用通知,此时不会关心方法的输出是什么
返回通知(After-returning) 在目标方法成功执行之后调用通知
异常通知(After-throwing) 在目标方法抛出异常后调用通知
环绕通知(Around) 通知包裹了被通知的方法,在被通知的方法调用之前和之后执行自定义的行为
2、连接点(JoinPoint)

连接点是在应用执行过程中能够插入切面的一个点。

3、切点(PointCut)

一个切面并不需要通知应用的所有连接点,切点有助于缩小切面所通知的连接点范围。如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”。因此,切点其实就是定义了需要执行在哪些连接点上执行通知。

4、切面(Aspect)

切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和在何处完成其功能。

5、引入(Introduction)

引入允许我们向现有的类添加新方法或属性。

6、织入(Weaving)

织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期中有很多个点可以进行织入。

自定义接口

我们需要通过自定义接口来指定需要打印入参出参的方法。
value()是我们输出日志时的标识,默认为""
ignore()我们可以用来设置注解是否要被忽略,默认为false
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
   
   

    String value() default "";

    boolean ignore() default false;
}

定义切面

我们这里用到了@Around 环绕通知,能够在方法调用的前后执行这串代码逻辑。

@Pointcut指定切点,这里通过指定带有@Log注解的方法/类为切点。

通过反射获取到方法名、入参、出参。
import com.xxx.utils.JsonUtils;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

<think>好的,我现在需要回答用户的问题:“针对上述项目,如何采用AOP对所有自定义HTTP接口方法进行日志打印,请分析描述”。首先,我需要回忆AOP的基本概念和在Spring Boot中的应用方式。用户提到的项目涉及HTTP接口,所以需要拦截控制器(Controller)层的方法,记录。 首先,我需要确定如何定义AOP的切点(Pointcut)。因为目标是所有自定义HTTP接口,通常这些接口位于@RestController或@Controller注解的类中,方法上可能有@RequestMapping、@PostMapping等注解。因此,切点可以定义为匹配这些类中的所有方法。 接下来,需要选择合适的通知类型(Advice)。记录适合在方法执行前(@Before),但更准确的是在方法执行前后,使用@Around环绕通知,这样可以获取方法,执行方法获取返回值(),并记录完整的调用信息。 然后,需要获取方法。在环绕通知中,可以通过JoinPoint获取方法数,但需要注意处理敏感信息(如密码),可能需要脱敏处理。同时,需要获取请求的URI、方法类型(GET/POST等),这些信息可以通过HttpServletRequest获取,需要注ServletRequestAttributes来获取当前请求。 关于的记录,环绕通知中执行proceed()方法后可以获取返回值,然后记录返回内容。需要注意异常情况,可能需要在catch块中记录异常信息。 另外,需要考虑日志的格式和级别,确保信息清晰且不会影响性能。可能需要配置日志的输级别(如INFO),并避免记录过大的请求体导致日志文件膨胀。 还需要处理一些特殊情况,比如文件上传接口的可能包含MultipartFile,这时候直接打印可能不合适,需要特殊处理;或者异步方法日志记录,需要确保AOP能正确拦截。 最后,需要测试AOP是否生效,通过调用接口并检查日志是否包含信息,验证配置的正确性。 现在需要将这些思路整理成结构化的回答,包括关键步骤(如依赖配置、切点定义、环绕通知实现、敏感信息处理、测试验证等),并确保覆盖用户项目的实际需求。 </think>在 Spring Boot 项目中使用 AOP(面向切面编程)实现自定义 HTTP 接口的日志打印,核心是通过切面拦截控制器(Controller)层的方法,捕获请求数、响应结果及接口元信息(如 URI、请求方法)。以下是具体实现步骤和关键逻辑分析: ### **一、前置准备:添加 AOP 依赖** 需在 `pom.xml` 中引 Spring AOP 依赖(Spring Boot 项目默认已集成,若缺失需显式添加): ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` ### **二、定义 AOP 切点(Pointcut)** 目标是拦截所有自定义 HTTP 接口,这些接口通常位于被 `@RestController` 或 `@Controller` 注解的类中,且方法使用 `@RequestMapping`、`@PostMapping` 等注解。因此,切点需匹配以下范围: #### **方式1:匹配所有 Controller 类的方法** ```java @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)" + " || within(@org.springframework.web.bind.annotation.Controller *)") public void controllerPointcut() {} ``` #### **方式2:精确匹配 HTTP 接口方法** 若需排除部分接口(如健康检查接口 `/actuator/health`),可结合注解方法名规则: ```java @Pointcut("execution(* com.example.demo.controller.*.*(..))" + // 限定包路径下的 Controller " && !execution(* com.example.demo.controller.HealthController.*(..))") // 排除健康检查接口 public void httpApiPointcut() {} ``` ### **三、实现环绕通知(@Around)** 使用 `@Around` 通知拦截方法执行,可同时获取****(方法执行前)和****(方法执行后),并记录请求元信息(如 URI、请求方法)。关键步骤如下: #### **步骤1:获取请求元信息** 通过 `RequestContextHolder` 获取当前请求的 `HttpServletRequest`,提取 URI、请求方法(GET/POST 等): ```java ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String uri = request.getRequestURI(); // 接口 URI(如 /user/register) String method = request.getMethod(); // 请求方法(如 POST) ``` #### **步骤2:获取方法** 通过 `JoinPoint` 获取方法数列表。需注意: - 若数包含敏感信息(如密码、token),需脱敏处理(如替换为 `***`)。 - 若数是文件(`MultipartFile`),避免直接打印二进制内容,可记录文件名。 **示例代码**: ```java Object[] args = joinPoint.getArgs(); // 获取方法数数组 List<Object> filteredArgs = new ArrayList<>(); for (Object arg : args) { if (arg instanceof MultipartFile) { // 文件数仅记录文件名 filteredArgs.add("File: " + ((MultipartFile) arg).getOriginalFilename()); } else if (arg instanceof String && "password".equals(joinPoint.getSignature().getName())) { // 密码数脱敏 filteredArgs.add("***"); } else { filteredArgs.add(arg); } } String params = JSON.toJSONString(filteredArgs); // 转为 JSON 字符串(需引 fastjson 或 jackson) ``` #### **步骤3:执行方法获取** 通过 `ProceedingJoinPoint.proceed()` 执行目标方法获取返回值(): ```java Object result = proceedingJoinPoint.proceed(); // 执行被拦截的方法 String response = JSON.toJSONString(result); // 转为 JSON ``` #### **步骤4:记录完整日志** 将 URI、请求方法等信息输日志文件(如 `application.log`): ```java log.info("[接口请求] URI:{},方法:{},:{}", uri, method, params); log.info("[接口响应] URI:{},方法:{},:{}", uri, method, response); ``` ### **四、处理异常场景** 若接口抛异常,需在 `@Around` 中捕获并记录异常信息,避免日志丢失: ```java try { Object result = proceedingJoinPoint.proceed(); // 记录正常响应日志 } catch (Throwable e) { log.error("[接口异常] URI:{},方法:{},异常信息:{}", uri, method, e.getMessage()); throw e; // 重新抛异常,避免影响业务逻辑 } ``` ### **五、完整 AOP 切面示例** ```java 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.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import com.alibaba.fastjson.JSON; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Aspect @Component public class ApiLogAspect { private static final Logger log = LoggerFactory.getLogger(ApiLogAspect.class); // 定义切点:拦截所有 Controller 类的方法 @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)" + " || within(@org.springframework.web.bind.annotation.Controller *)") public void apiPointcut() {} @Around("apiPointcut()") public Object logApi(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 获取请求元信息 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String uri = request.getRequestURI(); String method = request.getMethod(); // 处理(脱敏、过滤文件) Object[] args = proceedingJoinPoint.getArgs(); String params = JSON.toJSONString(args); // 简化示例,实际需脱敏 // 记录请求日志 log.info("[接口请求] URI:{},方法:{},:{}", uri, method, params); // 执行方法获取响应 long start = System.currentTimeMillis(); Object result = proceedingJoinPoint.proceed(); long cost = System.currentTimeMillis() - start; // 记录响应日志(含耗时) String response = JSON.toJSONString(result); log.info("[接口响应] URI:{},方法:{},:{},耗时:{}ms", uri, method, response, cost); return result; } } ``` ### **六、关键注意事项** 1. **性能影响**:日志打印可能增加接口耗时,生产环境需控制日志级别(如仅 `INFO` 级别),避免打印大对象(如文件内容)。 2. **敏感信息脱敏**:需对密码、身份证号等敏感数做脱敏处理(如替换为 `***`),可通过自定义注解标记敏感字段,在 AOP 中动态处理。 3. **异步接口支持**:若接口是异步的(如返回 `CompletableFuture`),需在异步任务完成后记录响应,可通过监听 `CompletableFuture` 的完成事件实现。 4. **日志格式统一**:建议使用 JSON 格式输日志,方便后续通过 ELK(Elasticsearch+Logstash+Kibana)等工具进行日志分析和监控。 ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值