Java SDK for REST API服务的错误处理

Mar*_*rte 21 java api error-handling sdk exception

我们正在构建Java SDK以简化对提供REST API的服务的访问.此SDK将由第三方开发人员使用.我很难找到最适合在Java语言中实现错误处理的模式.

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

  • 401:用户未经过身份验证
  • 403:用户无权访问此照片
  • 404:没有带有该ID的照片

该服务看起来像这样:

interface RestService {   
    public Photo getPhoto(String photoID);
} 
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,我还没有解决错误处理问题.我显然希望为sdk的客户端提供一种方法来知道发生了哪个错误,以便从中恢复.Java中的错误处理是使用Exceptions完成的,所以让我们继续.但是,使用异常执行此操作的最佳方法是什么?

1.有一个关于错误信息的例外.

public Photo getPhoto(String photoID) throws RestServiceException;

public class RestServiceException extends Exception {
    int statusCode;

    ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

但是我真的不喜欢这个解决方案主要有两个原因:

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

2.有一个错误的类层次结构

方法签名仍然是:

public Photo getPhoto(String photoID) throws RestServiceException;
Run Code Online (Sandbox Code Playgroud)

但现在我们为每种错误类型创建例外:

public class UnauthenticatedException extends RestServiceException;
public class UnauthorizedException extends RestServiceException;
public class NotFoundException extends RestServiceException;
Run Code Online (Sandbox Code Playgroud)

现在,SDK的客户端可以执行以下操作:

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

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

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

3.有一个错误的类层次结构+在方法的签名中列出它们

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

public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;
Run Code Online (Sandbox Code Playgroud)

但是,将来可能会将新的错误情况添加到此休止端点.这意味着在方法的签名中添加一个新的Exception,这将是对java api的重大改变.我们希望有一个更强大的解决方案,在所描述的情况下不会导致对api的更改.

4.具有错误的类层次结构(使用未经检查的例外)+在方法的签名中列出它们

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

public class RestServiceException extends RuntimeException
Run Code Online (Sandbox Code Playgroud)

我们保留方法的签名:

public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;
Run Code Online (Sandbox Code Playgroud)

这样我就可以在不破坏现有代码的情况下为方法的签名添加新的异常.但是,使用此解决方案,开发人员不会被强制捕获任何异常,并且不会注意到他需要处理的错误情况,直到他仔细阅读文档(是的,正确!)或注意到方法签名中的异常.

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

我提到的那些还有其他(更好的)替代方案吗?

4gu*_*71n 16

异常处理备选方案:回调

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

    /**
     * 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();
Run Code Online (Sandbox Code Playgroud)

这就是回调类的样子:

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.
    }

}
Run Code Online (Sandbox Code Playgroud)

做这样的事情你可以定义一个请求生命周期,并管理请求的每个状态.您可以使某些方法可选,以实现与否.您可以获得一些常规错误,并让用户有机会实现处理,就像在onError中一样.

如何清楚地定义哪些异常处理?

如果你问我,最好的方法是绘制请求的生命周期,如下所示:

示例异常生命周期

这只是一个糟糕的例子,但重要的是要记住所有方法的实现,可能是或不是,可选项.如果onAuthenticationError是强制性的,那么也不是必须的,onBadUsername反之亦然.这使得这个回调变得如此灵活.

我是如何实现Http客户端的?

好吧,我对http客户端了解不多,我总是使用apache HttpClient,但http客户端之间没有太多差异,最多的是功能稍微多一点,但最后,它们都是一样的.只需拿起http方法,输入网址,参数,然后发送即可.对于这个例子,我将使用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;
    }

}
Run Code Online (Sandbox Code Playgroud)

这是一个示例实现RestRequest.这只是一个简单的例子,当你制作自己的休息客户时,有很多话题需要讨论.例如,"用什么样的json库来解析?","你是在为android还是为java工作?" (这很重要,因为我不知道android是否支持java 7的一些功能,如多捕获异常,并且有一些技术不适用于java或android和反之亦然).

但我能说的最好的就是根据用户编写sdk api的代码,请注意用于生成其余请求的行很少.

希望这可以帮助!再见:]


Rap*_*vet 10

看来你正在"手"做事.我建议你试试Apache CXF.

这是一个简洁的JAX-RS API实现,使您几乎可以忘记REST.它适用于(也推荐)Spring.

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

然后,CXF发挥了魔力.

您在Java代码中抛出正常的异常,然后在server/nd或client上使用异常映射器在它们和HTTP状态代码之间进行转换.

这样,在服务器/ Java客户端,您只处理常规的100%Java异常,并且CXF为您处理HTTP:您既可以使用明确的REST API,也可以使用Java Client,供用户使用.

客户端既可以从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 Unexpected/Unrecovable RunTimeException

它遵循REST和Java标准.


小智 3

我见过结合了你的建议 2 和 3 的库,例如

public Photo getPhoto(String photoID) throws RestServiceException, UnauthenticatedException, UnauthorizedException, NotFoundException;
Run Code Online (Sandbox Code Playgroud)

这样,当您添加新的扩展检查异常时RestServiceException,您不会更改该方法的约定,并且使用它的任何代码仍然可以编译。

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