记录异常并继续执行而不会使代码混乱

Log exceptions and continue execution without clutter on code

我正在考虑在我的应用程序上实施标准错误处理。我想要无法处理的错误(自定义未经检查的错误),但也不是灾难性的,可以通过故障屏障记录下来,而不必用讨厌的尝试捕获和记录器调用使我的代码混乱。

让我举例说明。 我有一个接收 json 字符串并将其设置到我的对象模型中的摄入量,所述模型中的一个字段调用 timeHelper 函数,如果参数无效(null 或空),该函数可以抛出异常。这个函数的产量对程序来说并不重要,事实上这个程序应该永远不会崩溃(尽我所能)因为它应该保持 24/7。

型号

public class MyModel{
   private string myField
   public void setMyField(String myfield){
       this.myField = Helper.DoStuff(myField)
   }
}

摄入量

public class Intake{
    public MyModel receiveJson(){
        return JacksonMagic(arguments,MyModel.class)
    }
}

帮手

Public class Helper{

    public String DoStuff(String myField){
        //Check that can throw exception
        //regular operation with return
    }
}

现在,当生活是美好的DoStuff returns 一个字符串,实际上永远不应该抛出异常,因为它暗示了 json 的来源,它是外部的我的应用程序,发送了 wrong/missing 信息。如果它真的发生了,我希望它被记录下来,这样我就可以调查发生了什么。我还想设置一个框架,可能使用 Spring AOP,来处理该日志记录。但是正如您从示例中看到的那样,我也希望继续执行,因为这不是一些应用程序中断的事情。

我正在寻找的执行流程是这样的 Intake > Model > Helper(THROW EXCEPTION) > Logger > Whoever Called Intake

再一次,我想在没有 try catch logger 调用 cluter 的情况下做到这一点

AOP 可以做到这一点吗?

Post 回答编辑 只是想在这里留下一些资源。

要为 AspectJ 编译设置 IDE,这篇文章真的很有帮助。 https://www.baeldung.com/aspectj

这不是异常的好用例。

异常表示您无法处理的事情,"exceptional" 您无法处理的事件。您说这是一种可能的情况这一事实将其从异常更改为用例,在这种情况下,在您的服务层中记录警告可能是最好的解决方案。

异常有它们的位置,但是过度使用它们会使代码更难遵循,因为它破坏了应用程序的 "flow"。不应使用异常来控制流程。

AOP,在我看来,在异常处理方面提供的很少。它充其量可以记录异常(也可以使用 ExceptionHandler 模式以更清晰的方式实现),但是它肯定不能触发您的代码继续,就好像它没有发生一样。

如果您还没有,请查看日志记录策略,它们在这种情况下非常有用。

底线是:如果您希望控制流继续,请不要抛出异常(已检查或未检查)。

好的,这里我们使用完整的 MCVE,假设您知道如何使用 AspectJ 编译器来编译您的项目。很抱歉用包名、导入等重复您的 类,但我希望您能看到所有详细信息:

首先我们需要我们的帮助程序,它会随机抛出未经检查的异常,以便稍后我们可以看到该方面的操作:

package de.scrum_master.app;

import java.util.Random;

public class Helper {
  private static final Random RANDOM = new Random();

  public static String doStuff(String myField) {
    if (RANDOM.nextBoolean())
      throw new RuntimeException("uh-oh!");
    return "processed " + myField;
  }
}
package de.scrum_master.app;

public class MyModel {
  private String myField;

  public void setMyField(String myField) {
    this.myField = Helper.doStuff(myField);
  }

  @Override
  public String toString() {
    return "MyModel(myField=" + myField + ")";
  }
}
package de.scrum_master.app;

public class Intake {
  public MyModel receiveJson(String... arguments) {
    return jacksonMagic(arguments, MyModel.class);
  }

  public MyModel jacksonMagic(String[] arguments, Class<?> clazz) {
    MyModel myModel = new MyModel();
    myModel.setMyField(arguments[0]);
    return myModel;
  }

  public static void main(String[] args) {
    for (int i = 0; i < 10; i++)
      System.out.println(new Intake().receiveJson("foo"));
  }
}

现在,当您通过 Intake.main 运行 小驱动程序应用程序时,您将在控制台上看到未处理的异常。下面是如何使用方面来处理这个问题。我将方面限制为将所有方法执行与 String return 类型匹配,每当发生异常时愚蠢地 returning 一个虚拟值。您只需在其中添加您认为合适的更复杂的逻辑,并调整方面的切入点以匹配您要处理的方法。

package de.scrum_master.aspect;

public aspect ErrorHandler {
  String around() : execution(String *(..)) {
    try {
      return proceed();
    } catch (Exception e) {
      System.out.println("Exception handled: " + e);
      return "dummy";
    }
  }
}

我喜欢富有表现力的原生 AspectJ 语法,但我知道有些人出于某种原因对基于注释的语法感觉更舒服。看看 throws 声明、字符串常量中的切入点、显式连接点声明、强制转换 - 糟糕!不管怎样,我们开始吧:

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class ErrorHandler {

  @Around("execution(String *(..))")
  public String handleErrors(ProceedingJoinPoint thisJoinPoint) throws Throwable {
    try {
      return (String) thisJoinPoint.proceed();
    } catch (Exception e) {
      System.out.println("Exception handled: " + e);
      return "dummy";
    }
  }
}

控制台日志看起来像这样,方面就位:

MyModel(myField=processed foo)
MyModel(myField=processed foo)
Exception handled: java.lang.RuntimeException: uh-oh!
MyModel(myField=dummy)
MyModel(myField=processed foo)
Exception handled: java.lang.RuntimeException: uh-oh!
MyModel(myField=dummy)
Exception handled: java.lang.RuntimeException: uh-oh!
MyModel(myField=dummy)
Exception handled: java.lang.RuntimeException: uh-oh!
MyModel(myField=dummy)
MyModel(myField=processed foo)
Exception handled: java.lang.RuntimeException: uh-oh!
MyModel(myField=dummy)
MyModel(myField=processed foo)