是否应在JNI接口中引发通用或专用异常?

如何解决是否应在JNI接口中引发通用或专用异常?

场景

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() );
}
  1. 本机抛出通用Java异常,将其捕获到Java类中,然后重新抛出为特殊异常:
// 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。
  • 这两种选择都不会对我们产生重大影响。我只是出于兴趣而问。

解决方法

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

通常,Java包装程序独立于异常情况而有意义,这使得本机功能看起来更像Java。

异常对象的作用

异常对象用于与调用方(在调用堆栈的某个地方,捕获了异常)进行通信,告知某些方法调用失败的原因。对于典型的代码,该原因是无关紧要的(1),足以了解该故障并能够生成有意义的日志条目以及一条消息给用户。

与本机代码通信失败

您选择通过C ++顶层创建和引发Java异常来与本机代码进行通信,从而创建对Java异常系统和您选择使用的特定异常类的依赖。

我看到一些选择:

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

通信失败的用户代码

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

在您的情况下,原因隐藏在来自C ++ runtime_error的文本中。您可能会试图将其映射到其他适当的Java异常类型中,但是会“您将不需要它(YAGNI)”。

我的首选是NativeCppException之类的东西,它总结了C ++世界中可能发生的一切。某些呼叫者可能很勇敢地捕捉到这样的异常,可能只有在他有一个非本机可用的替代者的情况下。

(脚注1)

我知道对于单个异常类型的重要性存在不同的观点,但是我还没有找到令人信服的论据来证明通常在野外看到的极其复杂的异常类型层次结构。

将创建异常类型以供代码的某些部分使用,否则,这是经典的YAGNI案例。

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

  1. 该方法没有后备策略,因此在发生某些内部故障的情况下,整个方法都会失败。通常,这些方法会让异常在不经过干预的情况下不断蔓延。
  2. 方法具有一个后备策略,即使在某些内部调用失败之后也可以成功,通常通过重试或切换到其他执行路径来实现。如果存在这种后备策略,则通常可以使用它,而与故障原因无关。这些方法捕获特定块中出现的所有异常,然后激活后备,而与失败原因无关。
  3. 方法具有一个仅适用于特定情况的后备策略,并且可以通过异常类型来区分这些情况。这些方法仅捕获某些特定的异常类型,然后激活适当的后备。
  • 绝大多数方法都属于第一类(或者应该归为此类,如果不是过度设计的话)。

  • 对于某些方法,开发人员可以创建后备策略。通常,不仅针对特定故障,而且针对任何故障尝试该策略都无害。

  • 在极少数情况下,失败原因与选择原本不适当的后备有关,例如如果数据库通过异常告诉我密码已经过期,则代码可以将我重定向到密码更新过程,然后继续执行(一个人为设计的示例)。

实际的异常类型仅在第三种方法类型中起作用,我敢打赌,只有极少数的异常类型以这种方式使用,所以我们有YAGNI的经典案例。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 <property name="dynamic.classpath" value="tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-