如何在 java 中创建自定义编译器警告?

How can I create custom compiler warnings in java?

我正在寻找类似于实现 java.lang.AutoCloseable 接口的东西,其中会生成指示 Resource leak: 'xxxx' is never closed 的编译器警告。

此用例位于 java 中同步集合的包装器中。包装器有一个内部信号量来防止集合的并发修改。

它允许对集合进行原子操作,在这种情况下,信号量是在内部获取和释放的。它还允许从外部获取锁,提供唯一的密钥,使用该密钥可以在集合上执行操作。密钥必须在 "transaction".

结束时释放

我的目标是在同一方法中获取锁但未释放锁时创建编译器警告,以防止死锁。可以防止这种情况的替代设计解决方案也是可以接受的。

这是一个有趣的小问题,所以我很感激任何对它的见解。

为了创建编译器警告,您需要扩展 Eclipse 编译器。

另一种解决方案是在软件质量分析系统中创建自定义检查,例如 TeamscaleSonarQube。 自定义检查执行代码的静态分析(通常基于富含语义信息的抽象语法树)并在检测到不可靠代码时产生问题。这些问题显示在质量分析系统的用户界面上。 Eclipse 插件允许在 Eclipse 中集成系统,以便也可以在其中列出问题。

如你所说

An alternative design solution that would prevent this is also acceptable.

所以这里是:作为替代设计解决方案,使用函数式编程

与其找出错误,不如首先阻止错误的发生?

由于缺少您的源代码,我对您的代码做出一些假设:

  • Semaphore 是您的 class(或接口),它为您的 SynchronizedCollection.
  • 提供信号量
  • Semaphore提供了两种方法obtain()release().

您实际面临的问题是 State 的问题。 状态改变 导致时间耦合obtain()release()必须按顺序调用。您可以使用 函数式编程 中的元素作为替代设计。

Semaphore 目前看起来像这样:

public class Sempahore {
    // ...
    public void obtain() {
        // Lock code
    }
    public void release() {
        // Release code
    }
}

Semaphore 用户当前看起来像这样:

semaphore.obtain();
// Code protected by the Sempahore.
semaphore.release();

解决方案是将obtain()release()组合成一个函数,该函数将要保护的代码作为其参数。这种技术也称为传递块,或更正式地称为高阶函数 - 将另一个函数作为参数或return是另一个函数。

Java 也有函数指针,不是直接的,而是通过对接口的引用间接的。从Java8开始,只有一个抽象方法的接口甚至被称为Functional Interface,而Java8提供了一个optional[=103] =] 注释 @FunctionalInterface

因此,您的 class Sempahore 可能看起来像这样:

public class Semaphore {
    // ...
    private void obtain() {
        // Lock code
    } 
    private void release() {
        // Release code
    }
    public <V> void protect(final Callable<V> c) throws Exception {
        obtain();
        try {
            return c.call();
        } finally {
            release();
        }
    }
}

在 Java 7 及更早版本中,调用者看起来像这样:

semaphore.protect(new Callable<Object>() {
    public Object call() {
        // Code protected by the Semaphore.
    }
});

在 Java 8 和更新版本中,代码也可能如下所示:

semaphore.protect(() -> {
    // Code protected by the Semaphore.
});

关于此解决方案的怪癖

关于 Java 的一个方面在这种情况下完全糟糕:异常处理。通过函数式编程,迫切需要解决这个问题,但 Oracle 没有。我仍然希望 Java 9,但这对所有损坏的 API 都无济于事,例如 java.util.stream 已经在野外了。 Java 8 仍然保持处理或声明-检查异常的规则,但是函数式编程并没有很好地考虑到这一点。

有一些解决方法:

  • 如果不需要 return 个值,请使用 Runnable
  • 使用您自己的 Callable 接口,为异常声明类型参数。

我敢打赌使用 Runnable 是直截了当且不言自明的,因此我不会详细说明。

使用您自己的 Callable 界面版本如下所示:

public interface ProtectedCode<V,E> {
    V call() throws E;
}

public class Semaphore {
    // ...
    private void obtain() {
        // Lock code
    } 
    private void release() {
        // Release code
    }
    public <V, E> void protect(final ProtectedCode<V, E> c) throws E {
        obtain();
        try {
            return c.call();
        } finally {
            release();
        }
    }
}

现在,只要类型参数 E 的有限(因为它只能反映一种类型,而不是类型集)类型推断导致合理的结果,您就不需要乱用 Exception编译器。

如果您想对用户特别友好,实际上可以提供 protect 方法的三种变体:

  • public void protect(final Runnable r)
  • public <V> V protect(final Callable<V> c) throws Exception
  • public <V,E> V protect(final ProtectedCode<V,E> c) throws E

虽然@Christian Hujer 确实提供了可靠的解决方案,但我选择了另一条行之有效的路线。

SynchronizedCollection 周围有一个包装器 class "Resource",其中包含:

  • 用于锁定集合的信号量
  • 一个随机生成的 ID,表示当前持有的锁的密钥
  • 对集合执行原子操作的方法(获取锁,执行操作,立即释放)
  • 对集合执行非原子操作的方法(它们接受一个 ID 作为键,如果提供的键与当前持有锁的键匹配,则执行请求的操作)

上面描述的 class 足以为集合提供足够的保护,但我想要的是如果锁未释放则编译器警告。

为了实现这一点,有一个 "ResourceManager" 实现了 java.lang.AutoCloseable

这个class:

  • 通过构造函数
  • 传递了对"Resource"的引用
  • 获取构造函数中引用的锁
  • 提供一个 API 用于调用 "Resource" 上的非原子方法,使用它在构建期间获得的密钥
  • 提供一个 close() 方法,重写 java.lang.AutoCloseable,释放在构建过程中获得的锁

资源管理器是在需要对资源执行多个操作的任何地方创建的,如果未在任何特定代码路径上调用 close(),则会生成编译器警告。此外,在 java 7+ 中,可以在 try-with-resource 块中创建管理器,并且无论块中发生什么,锁都会自动释放。