问题描述
我正在尝试使用接口中的新默认函数,并且需要一种方法来为一组类提供类似 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
使用 WeakReference
和 WeakReference
对于垃圾收集器来说处理成本相对较高。引用队列在适当的 GC 运行后处理,许多 JVM 中的默认设置是只有一个线程来处理它们。这会减慢(在这种情况下)WeakHashMap
条目的回收速度,这会对性能产生连锁反应。
我花了一些时间才弄清楚这应该如何工作。 (我忘记了 tags
是隐式 static final
因为它是在接口中声明的。呃!)我认为它可能会作为一个“mixin”来向一个类添加一个“有一个标签”的特征.然而,这是一种昂贵的方法。在实现 private Tag<?> tag
的类中添加 Resource
字段 + getter 和 setter 会更有效率。
如果将 tags
重命名为 TAGS
,代码会更容易理解(对于注重标识符命名提示的老派 Java 程序员)。
好的...我看到您的解决方案的两个问题:
- 对
tags
的访问是不安全的,任何其他类都可以操纵它。更多内容见下文。 - 就速度而言,检索不是最佳的。然而,这在 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
类从一开始就被替换为相同类名的另一个实现,则这仍然可能被欺骗。
通常这很好,但一旦您开始使用字段成员,我就会考虑使用抽象类并使您的地图成为私有/最终地图。
查看以下内容以获取更多信息...