目的
通过方法拦截器,获取指定的参数和值,记录日志,以json的方式打印出来,并且支持自定义key;
技术选型
由于方法的参数是不确定的,可能是简单对象,也可能是复杂对象,这个时候,适合的的取值方式就是使用spring spel的方式.
具体实现
拦截器是基于注解的方式,首先定义注解
既然是记录方法调用日志,那么就允许方法注解
并且子注解提供自定义参数
@Target(ElementType.METHOD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface BizLog {
String value() default "";
@AliasFor("value")
String name() default "";
LogParam[] params() default {};
}
@Target(ElementType.TYPE_PARAMETER)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface LogParam {
String name() default "";
String key() default "";
}
拦截器主要实现方法拦截和参数的解析和值获取
@Aspect @Component不能少
@Aspect
@Component
public class BigLogMethodAspect {
private ExpressionParser parser = new SpelExpressionParser();
private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
@Pointcut("@annotation(bizLog)")
public void pointcut(BizLog bizLog) {
}
@Around("pointcut(bizLog)")
public Object around(ProceedingJoinPoint point, BizLog bizLog) throws Throwable {
System.out.println("方法拦截开始");
Method method = this.getMethod(point);
Object[] args = point.getArgs();
EvaluationContext context = this.bindParam(method, args);
LogParam[] logParams = bizLog.params();
Map<String, Object> map = new HashMap<>();
for (LogParam param : logParams) {
Expression expression = parser.parseExpression("#" + param.key());
Object value = expression.getValue(context);
map.put(param.name(), value);
}
Gson gson = new Gson();
String s = gson.toJson(map);
System.out.println("方法拦截结束: " + s);
return point.proceed();
}
private Method getMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
Method targetMethod = pjp.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes());
return targetMethod;
}
private EvaluationContext bindParam(Method method, Object[] args) {
String[] params = discoverer.getParameterNames(method);
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < params.length; i++) {
context.setVariable(params[i], args[i]);
}
return context;
}
}
最后进行测试
@Service
public class BizLogTest {
@BizLog(value = "测试业务", params = {
@LogParam(name = "主键", key = "id"),
@LogParam(name = "用户名", key = "person.name")
})
public void update(String id, Person person) {
}
}
@SpringBootTest
public class BizLogApplicationTest {
@Autowired
private BizLogTest bizLogTest;
@Test
public void test() {
Person person = new Person();
person.setId(1L);
person.setName("张三");
bizLogTest.update("1", person);
}
}
查看打印结果,完成了整个实现
具体的参数根据实际业务来定义,即可实现任意的参数输出,接入到ELK就可以直观的看到日志了。