默认接口方法的日志记录

27

向所有Java专家致敬!

从Java8开始,接口中可以有默认实现(耶!)。 然而,当您想要在默认方法中记录日志时就会出现问题。

我感觉每次在默认方法中记录日志都调用.getLogger()不明智。

是的,一个解决方法是在接口中定义静态变量,但这并不是良好的接口实践 + 它会暴露日志记录器(必须是public)。

目前我的解决方案:

interface WithTimeout<Action> {

    default void onTimeout(Action timedOutAction) {
        LogHolder.LOGGER.info("Action {} time out ignored.", timedOutAction);
    }

    static final class LogHolder {
        private static final Logger LOGGER = getLogger(WithTimeout.class);
    }
}

LogHolder任然对所有人可见,这并没有太多意义,因为它不提供任何方法并且应该是接口内部的。

你们中有人知道更好的解决方案吗?:)

编辑:我使用由Logback支持的SLF4J


我有一种感觉,每次想在默认方法中记录日志时调用.getLogger()可能不明智。你是否进行了性能测量来支持这种感觉?如果没有,那么如果不需要,请不要为性能进行优化。 - Ray
1
在我看来,公开一个LogHolder类或一个公共的Logger LOGGER没有太大区别。 - assylias
1
我没有看到任何理由可以_yay!_ - guido
如果有多个不同的类在使用这个记录器,我不明白这如何有所帮助。 - Tomáš Zato
@guido,完全没问题,祝你有美好的一天。 - Tomas Zaoral
显示剩余4条评论
2个回答

22

从JDK 16开始,您可以将辅助类隐藏在方法内部:

interface WithTimeout<Action> {

    default void onTimeout(Action timedOutAction) {
        logger().info("Action {} time out ignored.", timedOutAction);
    }

    private static Logger logger() {
        final class LogHolder {
            private static final Logger LOGGER = getLogger(WithTimeout.class);
        }
        return LogHolder.LOGGER;
    }
}

自JDK 9以来,接口允许具有private方法。为了利用这一点进行惰性初始化,我们需要能够在局部类中声明static字段,这在JDK 16中是允许的。

对于旧版Java,如果您不想将类LogHolder公开到公共领域,请勿将其作为interface的成员类。几乎没有使它成为成员类的好处,您甚至都无法节省打字时间,因为无论它是成员类还是同一包内的类,您都必须使用持有者类的名称限定字段访问:

public interface WithTimeout<Action> {

    default void onTimeout(Action timedOutAction) {
        LogHolder.LOGGER.info("Action {} time out ignored.", timedOutAction);
    }
}
final class LogHolder { // not public
    static final Logger LOGGER = getLogger(WithTimeout.class);
}

缺点是在同一软件包内可见性受限。当然,一个软件包内只能有一个名为LogHolder的顶层类。


1
是的,但这并不限制同一包中的其他成员访问记录器本身。也许我太过于纯粹了。像样的程序员几乎不会使用不同类别的记录器,也不会让它通过代码审查,我想。 - Tomas Zaoral
1
由于接口无法拥有私有成员,因此无法避免这种情况。即使有一种方法可以将LogHolder设置为私有类,其他类仍然可以调用getLogger(WithTimeout.class)来获取相同的记录器实例,因此这样做并不值得。将该类移出接口是有用的,以避免使用实现细节污染API,但记录器不应与安全相关。 - Holger
1
@Saint Hill:只有当LogHolder是一个嵌套类时才有效,但这样它将隐式成为public,这也是这个问题的关键所在。你只能拥有一个privateLOGGER字段或一个非publicLogHolder类。 - Holger
1
@Arendv.Reinersdorff 然后你必须在 WithTimeout 类中冗余地编写 WithinTimeout...。但这将是一个合理的折衷方案。 - Holger
1
更新,由于JDK 16+提供了真正的解决方案。 - Holger
显示剩余5条评论

2

给你。

Logger是接口的私有成员。除了这个接口和其默认方法之外,没有其他人可以访问Test2中的任何内容。而且没有东西可以扩展Test2类。

没有人建议您这样做……但它确实有效!这是一个获取主接口类记录器的好方法,可能还是做一些不完全疯狂的事情的聪明方式。

这实际上与OP问题中的LogHolder相同,只是Test2类都是私有方法并且构造函数也是私有的,并且该类未标记为静态。

作为额外的奖励,它保持状态,静态和每个实例。(请不要在真正的程序中这样做!)

public class TestRunner {
    public static void main(String[] args) {
        Test test = new Test() {
        };
        test.sayHello("Jane");
        System.out.println("Again");
        test.sayHello("Bob");
    }
}
public interface Test {
    default void sayHello(String name) {
        Logger log = Test2.log;
        Test2 ref = Test2.getMine.apply(this);
        int times = ref.getTimes();
        for (int i = 0; i < times; i++) {
            System.out.println(i + ": Hello " + name);
        }
        log.info("just said hello {} times :)",times);
    }
    final class Test2 {
        private static final Logger log = LoggerFactory.getLogger(Test.class);
        private static final Map lookup = new WeakHashMap();
        private static final Function getMine = (obj) -> {
            return lookup.computeIfAbsent(obj, (it) -> new Test2());
        };
        private int calls = 0;
        private Test2() {
        }
        private void setCalls(int calls) {
            this.calls = calls;
        }
        private int getCalls() {return calls;}
        private int getTimes() {return ++calls;}
    }
}


3
你基本上是通过添加很多OP没有要求的东西来改变OP在提出这个问题时已经拥有的解决方案。类Test2仍然具有public可见性,并且无论您是否显式声明,它仍然是static。添加一个private构造函数是一个小的改进,但不改变整个类在Test接口API中可见的事实。没有理由将不鼓励使用的实例数据映射引入其中,因为OP从未要求将数据与实例关联。挖掘一个超过1年半的问题真的值得吗? - Holger
1
我只是在展示可以做什么,无论你喜不喜欢。除了神秘的LogHolder帮助类的可见性之外,这个解决方案没有任何问题。我认为很多人会欣赏看到他们所拥有的选择。在默认方法中需要一个记录器是真实存在的,这个解决方案(以及OP的解决方案)提供了一种方法来实现它。这只是确保LogHolder不能被扩展或实例化。 - The Coordinator

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接