Spring spel表达式通过拦截器实现日志记录

目的

通过方法拦截器,获取指定的参数和值,记录日志,以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 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就可以直观的看到日志了。

你可能感兴趣的:(Spring spel表达式通过拦截器实现日志记录)