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

REST API服务JavaSDK错误处理

计均
2023-03-14

我们正在构建一个JavaSDK,以简化对我们提供REST API的服务之一的访问。这个SDK将由第三方开发者使用。我正在努力寻找最好的模式来实现更适合Java语言的SDK中的错误处理。

假设我们有其余的endpoint:GET /photos/{photoId}。这可能会返回以下HTTP状态代码:

  • 401:用户未通过身份验证
  • 403:用户没有访问此照片的权限
  • 404:没有带那个身份证的照片

该服务看起来像这样:

interface RestService {   
    public Photo getPhoto(String photoID);
} 

在上面的代码中,我还没有提到错误处理。我显然想为sdk的客户机提供一种方法,让他们知道发生了什么错误,并从中恢复。Java中的错误处理是使用异常来完成的,所以让我们继续。然而,使用异常实现这一点的最佳方法是什么?

1.有一个关于错误信息的异常。

public Photo getPhoto(String photoID) throws RestServiceException;

public class RestServiceException extends Exception {
    int statusCode;

    ...
}

然后sdk的客户端可以执行以下操作:

try {
    Photo photo = getPhoto("photo1");
}
catch(RestServiceException e) {
    swtich(e.getStatusCode()) {
        case 401 : handleUnauthenticated(); break;
        case 403 : handleUnauthorized(); break;
        case 404 : handleNotFound(); break;
    }
}

然而,我并不喜欢这个解决方案,主要有两个原因:

  • 通过查看方法的签名,开发人员不知道他可能需要处理什么样的错误情况。
  • 开发人员需要直接处理HTTP状态代码,并知道它们在这个方法的上下文中的含义(显然,如果它们被正确使用,很多时候含义是已知的,但是情况可能并不总是如此)。

2.具有错误的类层次结构

方法签名仍然是:

public Photo getPhoto(String photoID) throws RestServiceException;

但现在我们为每种错误类型创建异常:

public class UnauthenticatedException extends RestServiceException;
public class UnauthorizedException extends RestServiceException;
public class NotFoundException extends RestServiceException;

现在SDK的客户端可以这样做:

try {
    Photo photo = getPhoto("photo1");
}
catch(UnauthenticatedException e) {
    handleUnauthorized();
}
catch(UnauthorizedException e) {
    handleUnauthenticated();
}
catch(NotFoundException e) {
    handleNotFound();
}

通过这种方法,开发人员不需要知道产生错误的HTTP状态代码,他只需要处理Java异常。另一个优点是,开发人员可能只捕获他想要处理的异常(与以前的情况不同,以前的情况是必须捕获单个异常(RestServiceExcture),然后才决定他是否想要处理它)。

然而,还有一个问题。通过查看方法的签名,开发人员仍然不知道他可能需要处理的错误类型,因为我们在方法的签名中只有超级类。

3.在方法的签名中列出错误的类层次结构

好的,现在想到的是将方法的签名更改为:

public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;

然而,将来可能会向这个restendpoint添加新的错误情况。这将意味着在方法的签名中添加一个新的异常,这将是对JavaAPI的一个突破性的更改。我们希望有一个更健壮的解决方案,在所描述的情况下不会导致api的中断更改。

4.让错误的类层次结构(使用未检查的异常)在方法的签名中列出它们

那么,未经检查的异常呢?如果我们更改RestServiceException以扩展RuntimeException:

public class RestServiceException extends RuntimeException

我们保留方法的签名:

public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;

这样,我可以在不破坏现有代码的情况下向方法的签名添加新的异常。然而,有了这个解决方案,开发人员不会被迫捕捉任何异常,也不会注意到他需要处理的错误情况,直到他仔细阅读留档(是的,没错!)或注意到方法签名中的异常。

在这种情况下,错误处理的最佳实践是什么?

除了我提到的那些,还有其他(更好的)选择吗?

共有3个答案

李昌勋
2023-03-14

我见过把你的建议2和3结合起来的图书馆,例如。

public Photo getPhoto(String photoID) throws RestServiceException, UnauthenticatedException, UnauthorizedException, NotFoundException;

这样,当您添加一个扩展了RestServiceException的新的选中异常时,您不会更改该方法的约定,任何使用它的代码仍在编译。

与回调或未经检查的异常解决方案相比,它的一个优点是可以确保新错误将由客户端代码处理,即使它只是一个常规错误。在回调中,不会发生任何事情,如果出现未经检查的异常,客户端应用程序可能会崩溃。

钱华晖
2023-03-14

看来你是靠“手”在做事。我建议你试试Apache CXF。

JAX-RS API是一个简洁的实现,它使您几乎忘记了REST。它与(也推荐)Spring玩得很好。

只需编写实现接口(API)的类。您需要做的是用JAX-RS注释对接口的方法和参数进行注释。

然后,CXF施展魔法。

在java代码中抛出正常异常,然后使用服务器/nd或客户端上的异常映射器在它们和HTTP状态代码之间进行转换。

这样,在服务器/Java客户端,您只处理常规的100%Java异常,CXF为您处理HTTP:您既有清晰的REST API的好处,也有Java客户端的好处随时可供您的用户使用。

客户机既可以从WDSL生成,也可以在运行时从接口注释的自省中构建。

见:

  1. http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Exceptionhandling
  2. http://cxf.apache.org/docs/how-do-i-develop-a-client.html

在我们的应用程序中,我们定义并映射了一组错误代码及其对应的异常:

  • 4xx预期/函数执行(如坏参数,空集等)
  • 5XX意外的/不可恢复的RunTimeExcema内部错误,不应该发生

它遵循REST和Java标准。

申颖逸
2023-03-14

异常处理替代方案:回调

我不知道这是否是一个更好的选择,但你可以使用回调。通过提供默认实现,可以使某些方法成为可选的。看看这个:

    /**
     * Example 1.
     * Some callbacks will be always executed even if they fail or 
     * not, all the request will finish.
     * */
    RestRequest request = RestRequest.get("http://myserver.com/photos/31", 
        Photo.class, new RestCallback(){

            //I know that this error could be triggered, so I override the method.
            @Override
            public void onUnauthorized() {
                //Handle this error, maybe pop up a login windows (?)
            }

            //I always must override this method.
            @Override
            public void onFinish () {
                //Do some UI updates...
            }

        }).send();

回调类是这样的:

public abstract class RestCallback {

    public void onUnauthorized() {
        //Override this method is optional.
    }

    public abstract void onFinish(); //Override this method is obligatory.


    public void onError() {
        //Override this method is optional.
    }

    public void onBadParamsError() {
        //Override this method is optional.
    }

}

这样做可以定义请求生命周期,并管理请求的每个状态。您可以选择是否实现某些方法。您可以得到一些常规错误,并让用户有机会实现处理,比如在OneError中。

我怎样才能清楚地定义哪些异常可以处理?

如果你问我,最好的方法是画出请求的生命周期,比如:

这只是一个糟糕的例子,但重要的是要记住,所有的方法实现,可能是或不是可选的。如果onAuthenticationError是必须的,那么onBadUsername也将是必须的,反之亦然。正是这一点使得这种回调非常灵活。

以及我如何实现Http客户端?

嗯,我对超文本传输协议客户端了解不多,我总是使用apache HttpClient,但是超文本传输协议客户端之间没有太大的区别,大多数客户端都有或多或少的功能,但最终,它们都只是一样。只需拿起超文本传输协议方法,放入url,params,然后发送。对于这个例子,我将使用apache HttpClient

public class RestRequest {
    Gson gson = new Gson();

    public <T> T post(String url, Class<T> clazz,
            List<NameValuePair> parameters, RestCallback callback) {
        // Create a new HttpClient and Post Header
        HttpClient httpclient = new DefaultHttpClient();
        HttpPost httppost = new HttpPost(url);
        try {
            // Add your data
            httppost.setEntity(new UrlEncodedFormEntity(parameters));
            // Execute HTTP Post Request
            HttpResponse response = httpclient.execute(httppost);
            StringBuilder json = inputStreamToString(response.getEntity()
                    .getContent());
            T gsonObject = gson.fromJson(json.toString(), clazz);
            callback.onSuccess(); // Everything has gone OK
            return gsonObject;

        } catch (HttpResponseException e) {
            // Here are the http error codes!
            callback.onError();
            switch (e.getStatusCode()) {
            case 401:
                callback.onAuthorizationError();
                break;
            case 403:
                callback.onPermissionRefuse();
                break;
            case 404:
                callback.onNonExistingPhoto();
                break;
            }
            e.printStackTrace();
        } catch (ConnectTimeoutException e) {
            callback.onTimeOutError();
            e.printStackTrace();
        } catch (MalformedJsonException e) {
            callback.onMalformedJson();
        }
        return null;
    }

    // Fast Implementation
    private StringBuilder inputStreamToString(InputStream is)
            throws IOException {
        String line = "";
        StringBuilder total = new StringBuilder();

        // Wrap a BufferedReader around the InputStream
        BufferedReader rd = new BufferedReader(new InputStreamReader(is));

        // Read response until the end
        while ((line = rd.readLine()) != null) {
            total.append(line);
        }

        // Return full string
        return total;
    }

}

这是一个示例实现的RestRequest。这只是一个简单的例子,当你制作自己的Rest客户时,有很多话题要讨论。例如,“什么样的json库用于解析?”,“你是为Android还是为java工作?”(这很重要,因为我不知道android是否支持java 7的一些功能,比如多捕获异常,还有一些技术不适用于java或android和viceversa)。

但我能说的最好的是根据用户编写sdk api,请注意,发出rest请求的行很少。

希望这有帮助!再见:]

 类似资料:
  • 问题内容: 我有一个新编码的GWT / GAE应用,该应用在客户端上使用RequestFactory和Editors,在背面使用自定义的Objectify DAO服务。 flush()然后persist()路径在成功时可以正常工作。客户端JSR 303也可以正常工作。 我的问题是如何触发服务器警告/错误并处理UI更新? 我在http://turbomanage.wordpress.com/2010

  • 问题内容: 我正在向我的API发出请求,并且正在使用AngularJS $ resource模块。它与$ http不同,因此我不知道如何处理错误。 我的服务: 我的控制器: 我想要类似这样的东西..如果我的API无法正常工作,我不知道如何处理错误。 问题答案: 您可以将错误处理程序作为第二个参数传递给。 编辑: 为了使事情更清楚一些,例如:

  • 我的代码有问题。如何处理从服务到GSP的错误?我使用render from service或controller进行了尝试,但类似于[值为[{2}]的类[{1}]的属性[{0}]不是有效的电子邮件地址],并得到错误500:带有完整异常跟踪的内部服务器错误。我的消息来源: UserController.groovy

  • 问题内容: 我一直在尝试使此android服务正常工作,但我不知道为什么会收到此错误。 GiantBombAppActivity: 成就更新服务: 知道我在做什么错吗? 问题答案: 崩溃是因为您要返回的活页夹是BinderProxy的实例,而不是本地活页夹类。通常会发生这种情况,因为您的活动试图绑定到不在同一进程中的服务。跨流程边界绑定时,将使用BinderProxy实例代替实际实例(因为它在不同

  • 我得到以下错误 然后我改变了上面的渐变路径,然后出现了以下问题, 在构建我的项目时,我得到了这个异常。我重建并清理了项目,更新了google-play-services,然后也出现了错误。有人能帮我解决这个问题吗? Build.gradle

  • 我们正在尝试将Spring Cloud Netflix投入生产环境。目前,我们遇到了一个关于业务逻辑错误处理的问题。 我们使用Feign作为HTTP REST客户端。微服务A需要调用部署在不同JVM(或物理服务器)中的微服务B。微服务B可能会返回一些属于业务的错误消息。例如,A需要从B查询订单信息,但订单ID可能不存在,因此B必须返回错误消息,告诉A此订单不存在。A必须从返回消息中执行判断,以确定