捕捉并重新抛出相同的自定义异常

huangapple go评论81阅读模式
英文:

catching and re throwing same custom exception

问题

我有一个Spring JobRunner组件其中有一个`run()`方法该方法会抛出我的自定义异常

```java
public Response run(final Request request, final String id) {
    try {
        execution = jobLauncher.run(job, parameters);
    } catch (JobExecutionAlreadyRunningException e) {
        throw new MyCustomException("已在运行中", e);
    } catch (JobRestartException e) {         
        throw new MyCustomException("重启异常", e);
    } 
    return generateResponse(request, id, execution);
}

在我的服务类中,我在process()方法中调用这个run()方法:

protected Response process(final Request request, final String id) {
    //获取实体
    //保存
    //其他操作
    Response response;
    try {
        response = jobRunner.run(request, id);
        updateStatus(entity, response.getStatus(), "");
    } catch (MyCustomException ex) {
        updateStatus(entity, FAILED);
        throw new MyCustomException(ex.getMessage(), ex);
    }
}

你会注意到我在这里捕获了MyCustomException并重新抛出了它。我必须这样做,因为我需要在服务类中相应地更新状态,而服务类需要依赖于存储库来更新数据库中的状态,以便进行跟踪。

我还重新抛出异常,因为与这个自定义异常相关联的是一个用于API请求的ControllerAdvice,它会做出相应的响应。

我想知道的是,这是一个好的设计还是一个不好的实践?如果有什么需要更改的,我该怎么做?


<details>
<summary>英文:</summary>

I have a Spring JobRunner Component that has a `run()` method which throws my custom exception:

    public Response run(final Request request, final String id) {
        try {
            execution = jobLauncher.run(job, parameters);
        } catch (JobExecutionAlreadyRunningException e) {
            throw new MyCustomException(&quot;already running &quot;, e);
        } catch (JobRestartException e) {         
            throw new MyCustomException(&quot;Restart Exception&quot;, e);
        } 
        return generateResponse(request, id, execution);
    }

In my service class, I call this `run()` method inside `process()`

	  protected Response process(final Request request, final String id) {
		  //get entity
		  //save
		  //bla bla
        Response response;
        try {
            response = jobRunner.run(request, id);
            updateStatus(entity, response.getStatus(), &quot;&quot;);
        } catch (MyCustomException ex) {
            updateStatus(entity, FAILED);
            throw new MyCustomException(ex.getMessage(), ex);
        }
    }


You will notice I am catching `MyCustomException` and rethrowing again. I have to catch because I need to update the status accordingly here in the service class as it has a repository dependency to update status in db for tracking purpose. 

I am also rethrowing because attached to this custom exception is a `ControllerAdvice` for api requests so it responds accordingly. 

What I would like to know, is this a good design or a bad practice? What could I change if anything? 

</details>


# 答案1
**得分**: 2

这在不同情况下可能是一种良好的实践、不良的实践或不必要的,取决于情况。当你使用以下方式捕获并抛出新异常:

```java
throw new MyCustomException(ex.getMessage(), ex);

你会得到一个新的异常,它具有指向此代码行的 新堆栈跟踪,同时保留原始异常及其堆栈跟踪作为“cause”。

  • 如果原始异常是在异步上下文中产生的,这是一种良好的实践。响应式编程中经常出现这个问题:当某个异步操作创建异常时,其堆栈跟踪没有与调用代码的引用关联。在捕获点创建新异常会添加所需的上下文,以便找到导致异常的操作。

  • 如果原始异常包含应保密的 特权信息,这是一种不良的实践。在这种情况下,新异常应该在没有cause的情况下创建,而原始异常应该被记录。代码应该类似于:

log.error(ex);
throw new MyCustomException("出错了");
  • 在其他情况下,创建新的异常实例是不必要的。只需重新抛出原始异常。
} catch (MyCustomException ex) {
    updateStatus(entity, FAILED);
    throw ex;
}
英文:

This can be a good practice, bad practice, or unnecessary, depending on the case. When you catch and throw a new exception with:

throw new MyCustomException(ex.getMessage(), ex);

you get a new exception with a new stack trace that points to this line of code, and you maintain the original exception with its stack trace as the "cause".

  • This is a good practice if the original exception was produced in an asynchronous context. This is a common problem with reactive programming: when an exception is created by some asynchronous operation, its stack trace has no reference to the calling code. Creating a new exception at the point of capture adds the context needed to find the operation that resulted in the exception.

  • This is a bad practice if the original exception includes privileged information that should remain secret. In this case the new exception should be created without a cause, and the original exception should be logged. The code should look like:

    log.error(ex);
    throw new MyCustomException(&quot;whoopsie&quot;);
  • In other cases, creating a new exception instance is unnecessary. Just re-throw the original exception.
    } catch (MyCustomException ex) {
        updateStatus(entity, FAILED);
        throw ex;
    }

答案2

得分: 1

这实际上是一个非常好的问题。

我看到你正在通过层次来分离关注点。你应该对异常也采取同样的方式进行分离。

你还应该注意你抛出的异常的类型(已检查的异常 vs. 未检查的异常)。

JobCustomException

我建议为你的作业(Job)创建一个已检查的异常(Checked Exception)。在这里,你可以封装与作业错误相关的所有细节。通过使用已检查的异常,你可以实现两件事情:

  1. 你正在警告调用类可能会抛出并可以处理异常。
  2. 你正在向开发人员发出信号,这可能是一个可恢复的错误。

根据Java文档

> 如果客户端可以合理地预期从异常中恢复,将其设置为已检查的异常。如果客户端无法从异常中恢复,将其设置为未检查的异常。

ServiceCustomException

然后,可以为你的服务层创建另一个异常。如果你认为它可能不再被处理(当然除了ControllerAdvice),这种类型的异常应该扩展自RuntimeException。

还可以有助于转换或引入额外的信息。

最后,虽然你可能会有一个通用的作业异常,但许多客户类(服务)可能会抛出基于运行时的不同异常,以进一步添加有关发生错误的业务流程的业务细节。

抛出、捕获和重新抛出

只要你注意前面提到的要点,你做得很对。这就是抛出异常的全部意义。

异常不必是终端错误,尽管有一种观点认为RuntimeException是唯一被接受的异常类型。

大多数设计良好的对象API和框架都会使用适当的已检查异常,向客户类传达可能发生的错误信息。

英文:

This is a very good question actually.

I see that you are separating concerns by layers. You should do the same with your exceptions.

And you should also be aware of the type of exceptions you are throwing (checked vs. unchecked).

JobCustomException

I'd advise you to create a Checked Exception for your Jobs. Here you would encapsulate all the details related to a job error. And by using a checked exception, you are achieving two things:

  1. You are warning the calling class that an exception may be thrown and can be handled.
  2. You are signalling the developer that this may be a recoverable error.

From the Java Docs:

> If a client can reasonably be expected to recover from an exception,
> make it a checked exception. If a client cannot do anything to recover
> from the exception, make it an unchecked exception

ServiceCustomException

Then, another exception can be created for your service layer. This kind of exception should extend from RuntimeException if you believe it may no longer be handled (except for the ControllerAdvice, of course).

It may also be useful to transform or introduce extra information.

Finally, while you may have a single generic Job exception, there may be many client classes (services) that may throw different Runtime-based exceptions to add further business detail about the business flow where the error occurred.

Throwing, catching, and re-throwing

You are doing it right, as long as you pay attention to the aforementioned bullets. This is the whole point of throwing exceptions.

Exceptions don't need to be terminal errors, although there is some kind of belief that RuntimeExceptions are the only accepted type of Exceptions.

Most well-designed object's APIs and frameworks, make use of proper Checked exceptions to communicate to the client classes what are the errors that may occur.

huangapple
  • 本文由 发表于 2020年7月26日 06:46:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/63094333.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定