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

时间:2015-03-09 18:41:47

标签: java concurrency semaphore compiler-warnings

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

这个用例是在java中的Synchronized Collection的包装中。包装器有一个内部信号量,以防止同时修改集合。

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

结束时释放

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

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

3 个答案:

答案 0 :(得分:2)

正如你所说

  

另一种可以防止这种情况的设计解决方案也是可以接受的。

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

为什么不首先防止错误发生?

而不是发现错误。

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

  • Semaphore是您的班级(或界面),为您的SynchronizedCollection提供信号量。
  • Semaphore提供了两种方法obtain()release()

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

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()组合成一个函数,该函数将代码保护为其参数。此技术也称为传递块,或者更正式地称为高阶函数 - 一个将另一个函数作为参数或返回另一个函数的函数。

Java也有函数指针,不是直接的,而是间接地通过对接口的引用。从Java 8开始,只有一个抽象方法的接口甚至称为功能接口,Java 8为此提供了可选注释@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,但这并不会帮助那些像java.util.stream那样已经破坏了API的API。 Java 8仍然维护 handle-or-declare -rule的已检查异常,但函数式编程并没有很好地考虑到这一点。

有一些解决方法:

  • 如果您不需要返回值,请使用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();
        }
    }
}

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

如果您希望对用户非常友好,您实际上可以提供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

答案 1 :(得分:1)

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

另一种解决方案是在软件质量分析系统中创建自定义检查,例如 Teamscale SonarQube。自定义检查执行代码的静态分析(通常基于丰富了语义信息的抽象语法树,并创建问题,他们检测到狡猾的代码。问题显示在质量分析系统的用户界面上。 Eclipse插件允许在Eclipse中集成系统,以便可以在那里列出问题。

答案 2 :(得分:0)

虽然@Christian Hujer确实提供了一个可靠的解决方案,但我选择了另一条一直运作良好的路线。

有一个包装类&#34;资源&#34;在SynchronizedCollection周围包含:

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

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

要做到这一点,有一个&#34; ResourceManager&#34;它实现了java.lang.AutoCloseable

这堂课:

  • 传递对&#34;资源&#34;的引用。通过构造函数
  • 获取构造函数中引用的锁
  • 提供用于在&#34;资源&#34;上调用非原子方法的API。使用在施工期间获得的钥匙
  • 提供close()方法,覆盖java.lang.AutoCloseable,释放构造期间获取的锁

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