![](/img/trans.png)
[英]Catch a Hibernate exception upon SpringBoot application startup?
[英]Using monads in Springboot application to catch exceptions
因此,我们的项目后端是Java 8 Springboot应用程序,springboot允许您真正轻松地完成一些工作。 例如,要求验证:
class ProjectRequestDto {
@NotNull(message = "{NotNull.DotProjectRequest.id}")
@NotEmpty(message = "{NotEmpty.DotProjectRequest.id}")
private String id;
}
当不满足此约束时,spring(springboot?)实际上会引发验证异常,因此,我们在应用程序中的某个位置捕获了该异常,并为我们的应用程序构造了404(错误请求)响应。
现在,鉴于这一事实,我们在整个应用程序中都遵循相同的理念,也就是说,在应用程序的更深层中,我们可能会遇到以下情况:
class ProjectService throws NotFoundException {
DbProject getProject(String id) {
DbProject p = ... // some hibernate code
if(p == null) {
Throw new NotFoundException();
}
return p;
}
}
同样,我们在更高级别上捕获了此异常,并为客户端构造了另一个404。
现在,这引起了一些问题:
最重要的一点:我们的错误跟踪不再有用,当异常很重要时,我们无法(轻松地)进行区分,因为它们一直发生,因此,如果服务突然开始抛出错误,直到为时已晚,我们才会注意到。
大量无用的日志记录,例如在登录请求时,用户可能输入了错误的密码,因此我们将其记录下来,这是一个次要点:我们的分析无法帮助我们确定我们实际上在做错什么,我们看到了很多4xx,但这是我们的期望。
异常的代价很高,收集堆栈跟踪信息是一项资源密集型任务,这是目前的一个小问题,因为随着服务规模的扩大,这将成为一个更大的问题。
我认为解决方案非常明确,我们需要进行架构更改,以不将异常作为正常数据流的一部分,但这是一个很大的更改,而且我们的时间很短,因此我们计划随时间迁移,但是问题是短期内仍然存在。
现在,我的实际问题是:当我问一位建筑师时,他建议使用monads(作为c的暂时解决方案),因此我们不修改我们的体系结构,而是处理最污染的端点(例如错误的登录)。在短期内,但是我一直在为monad范式而苦苦挣扎,甚至在Java中也是如此,我真的不知道如何将其应用于我们的项目,您能帮我这个忙吗? 一些代码片段会非常好。
TL:DR:如果您使用的是一个普通的Spring Boot应用程序,该应用程序在其数据流中抛出错误,那么如何应用monad模式以避免登录不必要的数据量,并将此Error临时修复为数据流体系结构的一部分。
异常处理的标准monadic方法实质上是将结果包装为成功结果或错误类型。 它类似于Optional
类型,尽管在这里您有一个错误值而不是一个空值。
在Java中,最简单的实现如下所示:
public interface Try<T> {
<U> Try<U> flatMap(Function<T, Try<U>> f);
class Success<T> implements Try<T> {
public final T value;
public Success(T value) {
this.value = value;
}
@Override
public <U> Try<U> flatMap(Function<T, Try<U>> f) {
return f.apply(value);
}
}
class Fail<T> implements Try<T> {
// Alternatively use Exception or Throwable instead of String.
public final String error;
public Fail(String error) {
this.error = error;
}
@Override
public <U> Try<U> flatMap(Function<T, Try<U>> f) {
return (Try<U>)this;
}
}
}
(对于equals,hashCode,toString具有明显的实现)
先前您有可能返回T
类型结果或引发异常的操作的地方,它们将返回Try<T>
(可能是Success<T>
或Fail<T>
)的结果,而不会抛出,例如:
class Test {
public static void main(String[] args) {
Try<String> r = ratio(2.0, 3.0).flatMap(Test::asString);
}
static Try<Double> ratio(double a, double b) {
if (b == 0) {
return new Try.Fail<Double>("Divide by zero");
} else {
return new Try.Success<Double>(a / b);
}
}
static Try<String> asString(double d) {
if (Double.isNaN(d)) {
return new Try.Fail<String>("NaN");
} else {
return new Try.Success<String>(Double.toString(d));
}
}
}
即,您不会抛出异常,而是返回Fail<T>
值来包装错误。 然后,您可以使用flatMap方法编写可能会失败的操作。 应该清楚的是,一旦发生错误,它将短路任何后续操作-在上面的示例中,如果ratio
返回Fail
则不会调用asString
,并且错误会直接传播到最终结果r
。
以您的示例为例,在这种方法下,它看起来像这样:
class ProjectService throws NotFoundException {
Try<DbProject> getProject(String id) {
DbProject p = ... // some hibernate code
if(p == null) {
return new Try.Fail<DbProject>("Failed to create DbProject");
}
return new Try.Succeed<DbProject>(p);
}
}
与原始异常相比,它的优点是更具可组合性,例如,允许您在值的集合上映射(例如Stream.map)可失败的函数,并最终导致失败和成功的集合。 如果使用异常,则第一个异常将使整个操作失败,并且将丢失所有结果。
缺点之一是您必须在调用堆栈中一直使用Try返回类型(有点像检查异常)。 另一个原因是,由于Java没有内置的monad支持(例如la Haskell和Scala),因此flatMap的功能可能会有些冗长。 例如:
try {
A a = f(x);
B b = g(a);
C c = h(b);
} catch (...
f,g,h可能抛出的位置变为:
Try<C> c = f(x).flatMap(a -> g(a))
.flatMap(b -> h(b));
您可以通过使错误类型成为通用参数E
(而不是String)来概括上述实现,因此它变为Try<T, E>
。 这是否有用取决于您的要求-我从不需要它。
我在这里有一个更完整的版本,或者Javaslang和FunctionalJava库提供了它们自己的变体。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.