Java trait 解决方法

问题描述

我正在尝试使用接口中的新函数,并且需要一种方法来为一组类提供类似 trait 的行为。

我想到的是这个:

public interface Resource {

    Map<Resource,AnalysisTag<?>> tags = new WeakHashMap<>();

    @JsonIgnore
    default AnalysisTag<?> getTag() {
        return tags.get(this);
    }

    default Resource tag(AnalysisTag<?> tag) {
        tags.put(this,tag);
        return this;
    }
}

在这里,Resource 接口定义了实现类的行为,以便能够存储与特定对象关联的标签。当对象在代码的其他部分丢失所有引用时,WeakHashMap 会自动清理它。

WeakHashMaps 不会阻止其键的 GC。

我的问题是,这安全吗?我应该注意什么?

解决方法

我的问题是,这安全吗?

AFAIK ...是的...从某种意义上说它不应该导致内存泄漏。

有什么需要注意的吗?

一个问题是 WeakHashMap 使用 WeakReferenceWeakReference 对于垃圾收集器来说处理成本相对较高。引用队列在适当的 GC 运行后处理,许多 JVM 中的默认设置是只有一个线程来处理它们。这会减慢(在这种情况下)WeakHashMap 条目的回收速度,这会对性能产生连锁反应。

我花了一些时间才弄清楚这应该如何工作。 (我忘记了 tags 是隐式 static final 因为它是在接口中声明的。呃!)我认为它可能会作为一个“mixin”来向一个类添加一个“有一个标签”的特征.然而,这是一种昂贵的方法。在实现 private Tag<?> tag 的类中添加 Resource 字段 + getter 和 setter 会更有效率。

如果将 tags 重命名为 TAGS,代码会更容易理解(对于注重标识符命名提示的老派 Java 程序员)。

,

好的...我看到您的解决方案的两个问题:

  1. tags 的访问是不安全的,任何其他类都可以操纵它。更多内容见下文。
  2. 就速度而言,检索不是最佳的。然而,这在 Java 社区中被广泛接受,因为也存在像 ThreadLocal 这样的可憎之物。所以让我们接受这一点。

所以您需要做的是保护地图免受恶意访问。 我已经构建了三个具有越来越多的安全考虑的示例。

示例包结构:

stackoverflow.trait0r/
    AnalysisTag.java
    Trait0r.java
    /bad/
        BadResource.java
    /better/
        BetterResource.java
        BetterResourceContainer.java
    /best/
        BestResource.java
        BestResourceContainer.java
        SamePackageIllegalAccessTest.java

示例 1:您的,只是改进了一点

package stackoverflow.trait0r.bad;

import java.util.WeakHashMap;

import stackoverflow.trait0r.AnalysisTag;

public interface BadResource {
    /* best to always use full type (WeakHashMap instead of Map) when in control of code.
     * so downcast is always possible and upcast never necessary.
     * Plus access to the references object is usually faster
     */
    WeakHashMap<BadResource,AnalysisTag<?>> tags = new WeakHashMap<>();
    default AnalysisTag<?> getTag() {
        return tags.get(this);
    }
    default BadResource tag(final AnalysisTag<?> tag) {
        tags.put(this,tag);
        return this;
    }
}

示例 2:更好一点,使用受保护的包

package stackoverflow.trait0r.better;

import stackoverflow.trait0r.AnalysisTag;

public interface BetterResource {
    default AnalysisTag<?> getTag() {
        return BetterResourceConainer.tags.get(this);
    }
    default BetterResource tag(final AnalysisTag<?> tag) {
        BetterResourceConainer.tags.put(this,tag);
        return this;
    }
}

package stackoverflow.trait0r.better;

import java.util.WeakHashMap;

import stackoverflow.trait0r.AnalysisTag;

class BetterResourceConainer {
    static /* package */ WeakHashMap<BetterResource,AnalysisTag<?>> tags = new WeakHashMap<>();
}

示例 3:最佳保护:包保护和线程访问检查

package stackoverflow.trait0r.best;

import stackoverflow.trait0r.AnalysisTag;

public interface BestResource {
    default AnalysisTag<?> getTag() {
        return BestResourceConainer.getTag(this);
    }
    default BestResource tag(final AnalysisTag<?> tag) {
        BestResourceConainer.setTag(this,tag);
        return this;
    }
}

package stackoverflow.trait0r.best;

import java.util.WeakHashMap;

import stackoverflow.trait0r.AnalysisTag;

class BestResourceConainer {
    static private WeakHashMap<BestResource,AnalysisTag<?>> tags = new WeakHashMap<>();
    static private void ensureAccess() {
        final int CALLER_INDEX = 3;
        final StackTraceElement[] st = Thread.currentThread().getStackTrace();
        if (st.length < CALLER_INDEX + 1) throw new IllegalAccessError("Cannot access BestResourceConainer via external caller!");

        final StackTraceElement ste = st[CALLER_INDEX];
        final boolean grantAccess = BestResource.class.getName().equals(ste.getClassName());
        if (!grantAccess) throw new IllegalAccessError("Cannot access BestResourceConainer via external caller!");
    }
    static /* package */ void setTag(final BestResource pRes,final AnalysisTag<?> pTag) {
        ensureAccess();
        tags.put(pRes,pTag);
    }
    static AnalysisTag<?> getTag(final BestResource pRes) {
        ensureAccess();
        return tags.get(pRes);
    }
}

尝试一个肮脏的小黑客:

package stackoverflow.trait0r.best;

import stackoverflow.trait0r.AnalysisTag;

public class SamePackageIllegalAccessTest {

    public static void main(final String[] args) {
        try {
            BestResourceConainer.setTag(new BestResource() {/**/},new AnalysisTag<>("[ malicious hack ]"));
            System.out.println("Write successful!");
        } catch (final Error e) {
            System.out.println("Write access failed as expected");
        }

        try {
            final AnalysisTag<?> tag = BestResourceConainer.getTag(new BestResource() {/**/});
            System.out.println("TAG: " + tag);
//                      ThreadLocal<String> x;
        } catch (final Error e) {
            System.out.println("Read access failed as expected");
        }
    }

}

如您所见,如果您真的想要对 tags 地图进行某种程度的安全管理,有一些技术可以实现这一点。

但是请注意,您可能需要调整 CALLER_INDEX,并且如果 BestResource 类从一开始就被替换为相同类名的另一个实现,则这仍然可能被欺骗。

,

通常这很好,但一旦您开始使用字段成员,我就会考虑使用抽象类并使您的地图成为私有/最终地图。

查看以下内容以获取更多信息...

https://softwareengineering.stackexchange.com/questions/184945/why-is-an-interface-in-java-not-allowed-to-have-state

https://www.baeldung.com/java-weakhashmap