【问题标题】:Customized parameter logging when using aspect oriented programing使用面向方面编程时的自定义参数记录
【发布时间】:2016-12-22 20:08:48
【问题描述】:

我见过的所有示例都使用面向方面的编程来记录类、方法名称和持续时间,如果它们记录参数和返回值,它们只需使用 ToString()。我需要更好地控制记录的内容。例如,我想跳过密码,或者在某些情况下记录对象的所有属性,但在其他情况下只记录 id 属性。 有什么建议么?看了Java中的AspectJ和C#中的Unity拦截,没找到解决办法。

【问题讨论】:

    标签: aop aspectj unity-interception


    【解决方案1】:

    您可以尝试引入参数注释以使用某些属性来扩充您的参数。其中一个属性可以表示跳过记录参数,另一个可以用于为字符串表示指定转换器类。

    带有以下注释:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Log {
    }
    
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PARAMETER)
    public @interface SkipLogging {
    }
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PARAMETER)
    public @interface ToStringWith {
        Class<? extends Function<?, String>> value();
    }
    

    方面可能如下所示:

    import java.lang.reflect.Parameter;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    import java.util.stream.IntStream;
    
    import org.aspectj.lang.reflect.MethodSignature;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public aspect LoggingAspect {
    
        private final static Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    
        pointcut loggableMethod(): execution(@Log * *..*.*(..));
    
        before(): loggableMethod() {
            MethodSignature signature = (MethodSignature) thisJoinPoint.getSignature();
            Parameter[] parameters = signature.getMethod()
                .getParameters();
            String message = IntStream.range(0, parameters.length)
                .filter(i -> this.isLoggable(parameters[i]))
                .<String>mapToObj(i -> toString(parameters[i], thisJoinPoint.getArgs()[i]))
                .collect(Collectors.joining(", ", 
                        "method execution " + signature.getName() + "(", ")"));
            Logger methodLogger = LoggerFactory.getLogger(
                    thisJoinPointStaticPart.getSignature().getDeclaringType());
            methodLogger.debug(message);
        }
    
        private boolean isLoggable(Parameter parameter) {
            return parameter.getAnnotation(SkipLogging.class) == null;
        }
    
        private String toString(Parameter parameter, Object value) {
            ToStringWith toStringWith = parameter.getAnnotation(ToStringWith.class);
            if (toStringWith != null) {
                Class<? extends Function<?, String>> converterClass = 
                        toStringWith.value();
                try {
                    @SuppressWarnings("unchecked")
                    Function<Object, String> converter = (Function<Object, String>) 
                        converterClass.newInstance();
                    String str = converter.apply(value);
                    return String.format("%s='%s'", parameter.getName(), str);
                } catch (Exception e) {
                    logger.error("Couldn't instantiate toString converter for logging " 
                            + converterClass.getName(), e);
                    return String.format("%s=<error converting to string>", 
                            parameter.getName());
                }
            } else {
                return String.format("%s='%s'", parameter.getName(), String.valueOf(value));
            }
        }
    
    }
    

    测试代码:

    public static class SomethingToStringConverter implements Function<Something, String> {
    
        @Override
        public String apply(Something something) {
            return "Something nice";
        }
    
    }
    
    @Log
    public void test(
            @ToStringWith(SomethingToStringConverter.class) Something something,
            String string, 
            @SkipLogging Class<?> cls, 
            Object object) {
    
    }
    
    public static void main(String[] args) {
    // execution of this method should log the following message:
    // method execution test(something='Something nice', string='some string', object='null')
        test(new Something(), "some string", Object.class, null);
    }
    

    我在回答中使用了 Java 8 Streams API,因为它很紧凑,如果您不使用 Java 8 功能或需要更高的效率,您可以将代码转换为普通的 Java 代码。只是给你一个想法。

    【讨论】:

    • 感谢您的详细回复。这是他们通常使用基于拦截的日志记录的方式吗?方法声明看起来有点杂乱无章的所有注释,并且 ToStringConverter 不是类型安全的。当某些方面只会在运行时失败时,如何维护这段代码?
    • 我发布的这段代码只是对日志记录功能的初步了解。如果您觉得涉及记录方法的方法签名过于冗长,您可以基于类型引入有意义的默认值,在该类本身上声明记录行为,因此您可以跳过方法声明中的显式 @ToStringWith@SkipLogging 注释.这更像是一个示例,说明如何使用 AspectJ 进行这种跟踪/记录。
    • 类型安全性怎么样? ToStringConverter 必须从对象转换。没有类型安全的解决方案吗?
    • 你是对的,让你的转换器类做一个显式类有点尴尬,而且我可能为显式转换选择了错误的位置(即在转换器类中本身)。但问题是,我们不能没有显式转换,无论是在转换器类中,还是在我们创建转换器实例的方面本身。这是因为无法将注解值的 (@ToStringWith.value()) 泛型类型签名与应用注解的参数类型联系起来。但你是对的,在方面做演员可能会更好
    • 这种方法仍然困扰我的主要问题是我们失去了编译时的安全性。如果我更改参数的类型,我认为编译器不会发出警告。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-24
    • 2010-09-08
    • 2010-09-18
    • 2015-12-24
    • 1970-01-01
    • 2011-04-07
    相关资源
    最近更新 更多