使用面向方面编程时的自定义参数记录

时间:2016-12-22 20:08:48

标签: aop aspectj unity-interception

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

1 个答案:

答案 0 :(得分:0)

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

使用以下注释:

@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代码。这只是为了给你一个想法。