当前位置: 首页 > 知识库问答 >
问题:

应该在JNI接口中抛出通用或专用异常吗?

佴淮晨
2023-03-14

Java代码库使用C库。实现JNI接口是为了有一个API来使用Java调用访问本机方法。

到目前为止,实现这一点的方法是在Java端提供一个函数:

private static native void useFancyNativeFunction() throws SomeCustomException;

JNI头文件中的相应方法包括错误处理,在需要时反过来抛出Java异常:

try
{
    // do amazing C++ things
}
catch ( const std::runtime_error& e )
{
    jclass Exception = env->FindClass( "util/somestuff/SomeCustomException" );
    env->ThrowNew( Exception, e.what() );
}

这意味着,如果在本机代码的特定块中引发运行时异常,则会在JVM封装的应用程序中引发和处理自定义Java异常。

这引发了一场有趣的讨论,但尚未解决:

在此上下文中引发自定义异常意味着我们在JNI接口实现和特定Java代码库之间创建依赖关系。另一种方法是本机抛出通用Java异常,然后通过Java代码捕获它们,然后抛出专用异常。

因此,在给定场景中,有两种选择:

  1. 本机引发专门的自定义Java异常:
// Java caller
try
{
    useFancyNativeFunction();
}
catch( SomeCustomException e )
{
    // treat custom exception directly here
}

// Java native call
private static native void useFancyNativeFunction() throws SomeCustomException;

// C++ JNI header
try
{
    // do amazing C++ things
}
catch ( const std::runtime_error& e )
{
    jclass Exception = env->FindClass( "util/somestuff/SomeCustomException" );
    env->ThrowNew( Exception, e.what() );
}
// Java caller
try
{
    useFancyNativeFunction();
}
catch( RuntimeException e )
{
    // catch generic, throw specialized, handle elsewhere
    throw new SomeCustomException( e.getMessage() );
}

// Java native call
private static native void useFancyNativeFunction() throws RuntimeException;

// C++ JNI header
try
{
    // do amazing C++ things
}
catch ( const std::runtime_error& e )
{
    jclass Exception = env->FindClass( "java/lang/RuntimeException" );
    env->ThrowNew( Exception, e.what() );
}

首选哪一种?为什么?

  • 在我们的例子中,任何一个版本都没有缺点,因为只有一个Java代码库和一个C代码库。JNI接口不需要重用。
  • 依赖关系只存在于接口本身,而不存在于库中。后者正在被我们重用,但是没有Java参与。
  • 这两种选择都不太可能对我们产生重大影响。我只是出于兴趣。

共有1个答案

赫连棋
2023-03-14

主要问题是您是想自己重用本机库,还是总是将其与Java包装器捆绑在一起。

通常,Java包装器本身是有意义的,与异常问题无关,它使本机功能看起来更像Java。

异常对象用于向调用方(调用堆栈中捕获异常的位置)传递某些方法调用失败的原因。对于典型的代码,这个原因是不相关的(1),了解故障并能够生成一个合理的日志条目以及一条消息给用户就足够了。

通过让C顶层创建并抛出Java异常,您选择了从本机代码通信失败,这会创建对Java异常系统和您选择使用的特定异常类的依赖关系。

我看到了一些选择:

  • 使用Java标准异常,如RuntimeException。我们可以相信它会一直存在,这样依赖就不会产生任何问题。
  • 使用您自己的异常类型,如MyWonderfulLibraryException。我建议不要使用这样的命名。这不是描述失败的原因,而是描述失败的位置。您必须确保异常类在您的Java包装库中可用。
  • 使用您自己的异常类型,如NativeCppException。从技术上讲,它与前面的选择相同,但IMHO更好地将失败原因描述为无法在Java计算模型中适当描述的东西。
  • 从本机代码通信失败,而不创建Java异常,例如通过特殊的失败返回值。这可能比您当前的方法更容易(性能更好,并且创建更少的代码依赖)。

用户代码应该在失败的情况下看到一个异常,一个描述失败原因的异常(主要用于日志记录)。

在您的情况下,原因隐藏在来自Cruntime_error的文本中的某个地方。您可能会想将其映射到不同的适当Java异常类型,但“You Ain't Gonna必要性(YAGNI)”。

我的首选是像NativeCpexception这样的东西,它总结了C世界中可能发生的一切。一些调用方可能有足够的勇气捕捉到这样的异常,可能只有在他有一个非本地的替代方案可用的情况下。

(脚注1)

我知道关于个别异常类型的重要性存在不同的意见,但我还没有找到一个令人信服的论点来证明在野外经常看到的极其复杂的异常类型层次结构。

创建异常类型是为了供代码的某些部分使用,否则这是典型的YAGNI情况。

关于内部调用失败,方法通常分为三类:

  1. 方法没有后备策略,因此在某些内部故障的情况下,整个方法都会失败。通常,这些方法会让异常在没有干预的情况下产生涟漪。
  2. 方法有一个回退策略,即使在某些内部调用失败后也允许成功,通常是通过重试或切换到替代执行路径。如果存在这样的回退策略,则使用它通常是有意义的,与失败原因无关。这些方法捕获特定块中出现的所有异常,然后激活回退,与失败原因无关。
  3. 方法有一个只能应用于特定情况的回退策略,并且可以通过异常类型来区分情况。这些方法只捕获一些特定的异常类型,然后激活适当的回退。

>

对于一些方法,开发人员创建了后备策略。大多数情况下,不仅针对特定故障,而且针对任何故障尝试该策略都没有害处。

在极少数情况下,失败原因对于选择否则不合适的后备很重要,例如,如果数据库通过异常告诉我我的密码已过期,代码可以将我重定向到密码更新程序,然后继续(一个有点做作的例子)。

实际的异常类型只在第三种方法类型中起作用,我打赌只有很小比例的异常类型以这种方式使用,因此我们有一个经典的YAGNI案例。

 类似资料:
  • 我读过这段代码,其中接口抛出异常,但实现它的类没有抛出异常或捕获异常,这是为什么?它在java中是合法的还是安全的?

  • 最近我遇到了这段代码。 后来,它变成了这个项目的一般惯例。这是否被认为是一种良好的做法?

  • 问题内容: 当通常足以处理方法中的大多数条件失败时,为什么不建议抛出泛型(java.lang.Exception)异常?我知道,如果一个方法可以抛出多种类型的异常,那么抛出异常的特定子类可能会澄清一些处理,但是在一般的失败/成功案例中,我认为Exception的作用已绰绰有余。 问题答案: 问题在于,它也是的超类,其中包含一些不应捕获的内容,因为它表明编程存在问题,而不是由上下文引起的特殊情况。通

  • 抛出异常的行为是否可能抛出不同的异常? 为了抛出异常,必须(可选地)分配新对象,并调用其构造函数(隐式调用fillinstacktrace)。在某些情况下,听起来像addSupressed也被称为。那么如果没有足够的内存会发生什么呢?JVM是否需要预分配内置异常?例如,(1/0)会抛出OutOfMemoryError而不是ArithmeticException吗? 此外,构造函数是一个方法调用,因

  • 问题内容: 我们如何在代码中使用私有接口的方法? 抽象类是无法实例化的。因此,如果需要使用抽象类的方法,则可以继承它们并使用它们的方法。 但是,当我们谈论接口时,我们需要实现它们以使用它们的方法。 问题答案: 该关键字的意思是“任何人在同一类”: 这意味着在其中声明的所有类都可以使用该接口。 一个常见的用例是命令模式,其中接受例如字符串并将其转换为内部命令对象,这些对象都实现相同的接口。 如果将第

  • 你看?我应该编写多少额外的代码来向Api客户端返回正确的响应?此外,我可以忘记捕获一些可以正确报告的异常,它将作为默认的内部服务器异常返回。在controller中,我总是应该查看服务层,并检查哪些异常可以引发服务以正确处理它们。(请不要建议在java中检查异常)。 现在让我们来看看另一种解决方案: 在服务层(模型)中使用HttpStatus引发异常 就这样了。代码更少。现在我不必处理每一个可能引