问题描述:
生产上遇到一个问题,就是第三方厂商调用我们服务创建数据库实例,后面创建成功后,因为某些条件不适合,又调用卸载接口进行卸载了。
卸载后再次进行创建,创建成功。
但是过了一周后,第三方厂商人员反馈创建的数据库实例集群映射的DNS域名的ip还是之前旧的删除的哪套的,不是最新的。那么我去定位问题。无非就是去查看日志。
但是发现日志记录不全。所以我萌生了使用aop切面来记录请求和参数的想法。
于是就干起来了,实现主要有3点
1:打印日志的注解实现
@Target({ElementType.TYPE, ElementType.METHOD}) // 注解类型, 级别
@Retention(RetentionPolicy.RUNTIME) // 运行时注解
public @interface PrintLog {
String module() default ""; //方法标识,比如赋值为 新增数据库实例
boolean printParam() default true; //默认打印请求参数在日志中
}
2:切面定义
@Aspect // 切面标识
@Component // 交给spring容器管理
@Slf4j
public class PrintLogAspect {
@Autowired
/**
* 选取切入点为自定义注解
*/
@Pointcut("@annotation(cn.com.huacloud.cdd.annotation.PrintLog)")
public void PrintLog(){}
@Around(value="PrintLog()")
public Object printlog(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
PrintLog printLog = methodSignature.getMethod().getDeclaredAnnotation(PrintLog.class);
String module = printLog.module();
if(StringUtils.isEmpty(module)){
String classname = joinPoint.getTarget().getClass().getName();
String methodName= methodSignature.getMethod().getName();
module = classname+":"+methodName;
}
String paramsString = "";
// 是否要打印方法的参数数据
if (printLog.printParam()) {
// 参数名
String[] paramNames = methodSignature.getParameterNames();
if (paramNames != null && paramNames.length > 0) {
// 参数值
Object[] args = joinPoint.getArgs();
Map<String, Object> params = new HashMap<>();
for (int i = 0; i < paramNames.length; i++) {
Object value = args[i];
params.put(paramNames[i], value);
}
// 以json的形式记录参数
paramsString = JSONObject.toJSONString(params);
}
}
String username = "";
try{
LoginUser loginUser = SecurityUtil.getLoginUser();
username = loginUser.getNickName();
}catch(Exception e){
log.info("切面获取用户失败!");
e.printStackTrace();
}
try {
String startTime = DateUtil.now();
//统一打印执行前日志
log.info(StrUtil.format("当前时间{},用户{}请求进入方法模块{}开始,请求参数为{}!", startTime,username, module,paramsString));
// 执行原方法
Object object = joinPoint.proceed();
//统一打印执行后日志
String endTime = DateUtil.now();
log.info(StrUtil.format("当前时间{},用户{}请求进入方法模块{}开始,请求参数为{}", endTime,username, module,paramsString));
return object;
} catch (Exception e) {
// 备注记录失败原因
String exceptionTime = DateUtil.now();
log.info(StrUtil.format("异常时间{},用户{}请求进入方法模块{}异常,请求参数为{},异常原因是{}",exceptionTime,username, module,paramsString, e));
throw e;
}
}
}
3:注解使用方式
这样你在调用queryIMysqlInstance方法前后就会输出日志了。
疑难点:
获取当前方法所属的类: String classname = joinPoint.getTarget().getClass().getName();
备注:
在AOP编程中,我们经常会遇到下面的概念:
- Aspect:切面,即一个横跨多个核心逻辑的功能,或者称之为系统关注点;
- Joinpoint:连接点,即定义在应用程序流程的何处插入切面的执行;
- Pointcut:切入点,即一组连接点的集合;
- Advice:增强,指特定连接点上执行的动作;
- Introduction:引介,指为一个已有的Java对象动态地增加新的接口;
- Weaving:织入,指将切面整合到程序的执行流程中;
- Interceptor:拦截器,是一种实现增强的方式;
- Target Object:目标对象,即真正执行业务的核心逻辑对象;
- AOP Proxy:AOP代理,是客户端持有的增强后的对象引用。
引申思考:
这里提到了代理模式,后续我会对这里如何使用代理模式进行重点阐述。