Aop实现feign接口调用日志打印

231 阅读3分钟

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它允许开发者将横切关注点(cross-cutting concerns)从业务逻辑中分离出来。通过使用 AOP,可以在不改变业务逻辑的前提下,为应用程序添加额外的功能,如日志记录、事务管理、安全性等。

AOP 的核心概念

  1. 切面(Aspect)

    • 切面是一个模块,它定义了横切关注点的功能。通常由一个或多个通知和切点组成,控制器、服务和持久层中的若干公共交互逻辑。
  2. 通知(Advice)

    • 通知是切面执行的动作,定义了在什么时间(如方法执行前、执行后)执行特定的代码。主要类型有:

      • 前置通知(Before) :在目标方法执行之前执行。
      • 后置通知(After) :在目标方法执行之后执行。
      • 返回通知(After Returning) :方法返回之后执行,可以访问方法的返回值。
      • 异常通知(After Throwing) :如果方法抛出异常则执行。
      • 环绕通知(Around) :可以在方法执行前后自定义逻辑,通常用于进行性能监控或事务处理。
  3. 切点(Pointcut)

    • 切点定义了在哪些连接点(如方法调用)上应用通知,通常通过表达式指定,如匹配某个类的方法、某个包中的所有方法等。
  4. 连接点(Join Point)

    • 连接点是程序执行的一个特定点,比如方法调用、异常抛出等。AOP 主要关注的连接点是方法调用。
  5. 织入(Weaving)

    • 织入是将切面应用到目标对象的过程,它可以在编译时、类加载时或者运行时进行。在 Spring 中,通常是在运行时进行织入。

AOP 的优点

  1. 关注点分离

    • 使业务逻辑与横切关注点解耦,提升代码的可读性和可维护性。
  2. 代码重用

    • 相同的切面可以被多个类共享,避免重复代码。
  3. 增强功能

    • 可以在不修改现有代码的情况下,轻松地添加新的功能(如日志和事务管理)。

AOP 在 Spring 中的使用

Spring 框架内置了对 AOP 的支持,允许开发者方便地使用 AOP 特性来管理应用程序的各个方面。在 Spring 中,通常使用注解或 XML 配置来定义切面、通知和切点。 下面是Aop实现feign接口调用日志打印的例子:亲测可用

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.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;

/**
 * @author fly
 */
@Slf4j
@Aspect
@Component

public class FeignClientALoggingAspect {

    // 定义切面,拦截带有@FeignClient注解的方法
    @Around("@within(feignClient)")
    public Object logFeignRequests(ProceedingJoinPoint joinPoint, FeignClient feignClient) throws Throwable {
        // 获取serviceId
        String serviceId = getServiceIdFromFeignClient(joinPoint, feignClient);

        // 获取方法名
        String methodName = joinPoint.getSignature().getName();

        // 获取请求参数
        Object[] args = joinPoint.getArgs();

        // 将参数转换为字符串(这里需要根据你的参数类型进行转换)
        String requestParams = getRequestParamsAsString(args);

        // 打印日志信息,包括服务名、方法名和参数
        long startTime = System.currentTimeMillis();
        // 执行切点方法,并返回结果给调用者
        Object proceed = joinPoint.proceed();
        long executionTime = System.currentTimeMillis() - startTime;

        log.info("【调用服务】:{} 【方法】:{} 【请求参数】:{},【响应参数】:{},【耗时】:{}s", serviceId, methodName, requestParams, proceed, executionTime / 1000);
        return proceed;
    }

    // 从方法签名中提取服务名(这里假设你已经在FeignClient注解中指定了serviceId)
    private String getServiceIdFromFeignClient(ProceedingJoinPoint joinPoint, FeignClient feignClient) {
        if (joinPoint.getSignature() instanceof MethodSignature) {
            // 这里可以根据你的实际情况从方法上获取到服务名,比如通过注解或者自定义的元数据映射等。
            return feignClient != null ? feignClient.name() : "未知服务";
        } else {
            // 如果没有正确获取到方法签名或feignClient对象,返回未知。
            return "未知服务";
        }
    }

    // 这是一个示例方法,用于将请求参数转换为字符串(你可能需要根据实际的参数类型来实现这个逻辑)
    private String getRequestParamsAsString(Object[] args) {
        if (args == null || args.length == 0) {
            // 如果没有参数,返回相应的信息。
            return "";
        } else {
            StringBuilder paramsBuilder = new StringBuilder();
            for (Object arg : args) {
                if (paramsBuilder.length() > 0) {
                    // 参数之间用逗号分隔。
                    paramsBuilder.append(", ");
                }
                // 这里只是简单地将参数添加到字符串中,实际应用中可能需要更复杂的处理。
                paramsBuilder.append(arg);
            }
            // 返回转换后的字符串。
            return paramsBuilder.toString();
        }
    }
}