AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它允许开发者将横切关注点(cross-cutting concerns)从业务逻辑中分离出来。通过使用 AOP,可以在不改变业务逻辑的前提下,为应用程序添加额外的功能,如日志记录、事务管理、安全性等。
AOP 的核心概念
-
切面(Aspect) :
- 切面是一个模块,它定义了横切关注点的功能。通常由一个或多个通知和切点组成,控制器、服务和持久层中的若干公共交互逻辑。
-
通知(Advice) :
-
通知是切面执行的动作,定义了在什么时间(如方法执行前、执行后)执行特定的代码。主要类型有:
- 前置通知(Before) :在目标方法执行之前执行。
- 后置通知(After) :在目标方法执行之后执行。
- 返回通知(After Returning) :方法返回之后执行,可以访问方法的返回值。
- 异常通知(After Throwing) :如果方法抛出异常则执行。
- 环绕通知(Around) :可以在方法执行前后自定义逻辑,通常用于进行性能监控或事务处理。
-
-
切点(Pointcut) :
- 切点定义了在哪些连接点(如方法调用)上应用通知,通常通过表达式指定,如匹配某个类的方法、某个包中的所有方法等。
-
连接点(Join Point) :
- 连接点是程序执行的一个特定点,比如方法调用、异常抛出等。AOP 主要关注的连接点是方法调用。
-
织入(Weaving) :
- 织入是将切面应用到目标对象的过程,它可以在编译时、类加载时或者运行时进行。在 Spring 中,通常是在运行时进行织入。
AOP 的优点
-
关注点分离:
- 使业务逻辑与横切关注点解耦,提升代码的可读性和可维护性。
-
代码重用:
- 相同的切面可以被多个类共享,避免重复代码。
-
增强功能:
- 可以在不修改现有代码的情况下,轻松地添加新的功能(如日志和事务管理)。
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();
}
}
}