Java SDK for REST API服务的error handling
我们正在构建Java SDK以简化对提供REST API的服务的访问。 此SDK将由第三方开发人员使用。 我很难找到最适合在Java语言中实现error handling的模式。
假设我们有其余的端点: GET /photos/{photoId}
。 这可能会返回以下HTTP状态代码:
- 401:用户未经过身份validation
- 403:用户无权访问此照片
- 404:没有带有该ID的照片
该服务看起来像这样:
interface RestService { public Photo getPhoto(String photoID); }
在上面的代码中,我还没有解决error handling问题。 我显然希望为sdk的客户端提供一种方法来知道发生了哪个错误,以便从中恢复。 Java中的error handling是使用Exceptions完成的,所以让我们继续。 但是,使用exception执行此操作的最佳方法是什么?
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状态代码,他只需要处理Javaexception。 另一个优点是开发人员可能只捕获他想要处理的exception(与先前的情况不同,它必须捕获单个exception( RestServiceException
),然后才决定是否要处理它)。
但是,还有一个问题。 通过查看方法的签名,开发人员仍然不知道他可能需要处理的错误类型,因为我们在方法的签名中只有超类。
3.有一个错误的类层次结构+在方法的签名中列出它们
好的,现在想到的是将方法的签名更改为:
public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;
但是,将来可能会将新的错误情况添加到此hibernate端点。 这意味着在方法的签名中添加一个新的Exception,这将是对java api的重大改变。 我们希望有一个更强大的解决方案,在所述情况下不会导致对api的更改。
4.具有错误的类层次结构(使用未经检查的例外)+在方法的签名中列出它们
那么,未经检查的exception呢? 如果我们更改RestServiceException以扩展RuntimeException:
public class RestServiceException extends RuntimeException
我们保留方法的签名:
public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;
这样我就可以在不破坏现有代码的情况下为方法的签名添加新的exception。 但是,使用此解决方案,开发人员不会被强制捕获任何exception,并且不会注意到他需要处理的错误情况,直到他仔细阅读文档(是的,正确!)或注意到方法签名中的exception。
在这种情况下,error handling的最佳实践是什么?
我提到的那些还有其他(更好的)替代方案吗?
我见过结合你的建议2和3的库,例如
public Photo getPhoto(String photoID) throws RestServiceException, UnauthenticatedException, UnauthorizedException, NotFoundException;
这样,当您添加一个扩展RestServiceException
的新的已检查exception时,您不会更改方法的合同,并且使用它的任何代码仍然会编译。
与回调或未经检查的exception解决方案相比,优点是可确保客户端代码处理新错误,即使它仅作为一般错误。 在回调中,不会发生任何事情,并且在未经检查的exception情况下,您的客户端应用程序可能会崩溃。
exception处理备选方案:回调
我不知道它是否是更好的选择,但你可以使用回调。 您可以通过提供默认实现来使某些方法成为可选方法。 看看这个:
/** * 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. } }
通过这样做,您可以定义请求生命周期,并管理请求的每个状态。 您可以使某些方法可选,以实现与否。 您可以获得一些常规错误,并让用户有机会实现处理,就像在onError中一样。
如何清楚地定义哪些exception处理?
如果你问我,最好的方法是绘制请求的生命周期,如下所示:
这只是一个糟糕的例子,但重要的是要记住所有方法的实现,可能是或不是,可选项。 如果onAuthenticationError
是强制性的,那么onBadUsername
也不是必须的,反之亦然。 这使得这个回调变得如此灵活。
我是如何实现Http客户端的?
好吧,我对http客户端了解不多,我总是使用apache HttpClient,但http客户端之间没有太多差异,最多的是function稍微多一点,但最后,它们都是一样的 只需拿起http方法,输入url,参数,然后发送即可。 对于这个例子,我将使用apache HttpClient
public class RestRequest { Gson gson = new Gson(); public T post(String url, Class clazz, List 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的一些function,如多捕获exception,并且有一些技术不适用于java或android和反之亦然)。
但我能说的最好的就是根据用户编写sdk api的代码,请注意,用于生成其余请求的行很少。
希望这可以帮助! 再见:]
看来你正在“手”做事。 我建议你试试Apache CXF 。
这是一个简洁的JAX-RS API实现 ,使您几乎可以忘记REST。 它与(也推荐) Spring很好地配合。
您只需编写实现接口(API)的类。 您需要做的是使用JAX-RS注释来注释接口的方法和参数。
然后,CXF发挥了魔力。
您在Java代码中抛出正常的exception,然后在server / nd或client上使用exception映射器在它们和HTTP状态代码之间进行转换。
这样,在服务器/ Java客户端,您只处理常规的100%Javaexception,并且CXF为您处理HTTP:您既可以使用明确的REST API,也可以使用Java Client,供用户使用。
客户端既可以从WDSL生成,也可以在运行时从界面注释的内省构建。
见:
- http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Exceptionhandling
- http://cxf.apache.org/docs/how-do-i-develop-a-client.html
在我们的应用程序中,我们定义并映射了一组错误代码及其对应的exception:
- 4XX预期/function例外(如错误的参数,空集等)
- 对于“不应该发生”的内部错误,5XX Unexpected / Unrecovable RunTimeException
它遵循REST和Java标准。
解决方案可能因您的需求而异。
-
如果假设将来可能出现不可预测的新exception类型,则使用已检查exception层次结构的第二个解决方案以及抛出其超类
RestServiceException
是最佳解决方案。 所有已知的子类都应该像Subclasses: {@link UnauthenticatedException}, ...
一样列在javadoc中Subclasses: {@link UnauthenticatedException}, ...
,让开发人员知道可以隐藏哪种exception。 应该注意的是,如果某个方法只能从该列表中抛出少量exception,则应使用@throws
在此方法的javadoc中进行描述。 -
此解决方案也适用于
RestServiceException
所有外观意味着它的任何子类可能隐藏在它后面的情况。 但是在这种情况下,如果RestServiceException
子类没有它们的特定字段和方法,那么您的第一个解决方案是可取的,但需要进行一些修改:public class RestServiceException extends Exception { private final Type type; public Type getType(); ... public static enum Type { UNAUTHENTICATED, UNAUTHORISED, NOT_FOUND; } }
-
此外,还有一个很好的做法是创建替代方法,该方法将抛出未经检查的exception,该exception包含
RestServiceException
exeption本身,以便在“all-or-nothing”业务逻辑中使用。public Photo getPhotoUnchecked(String photoID) { try { return getPhoto(photoID); catch (RestServiceException ex) { throw new RestServiceUncheckedException(ex); } }
这一切都取决于您的API错误响应的信息量。 API的error handling信息越多,exception处理的信息就越多。 我相信例外只会像从API返回的错误一样提供信息。
例:
{ "status":404,"code":2001,"message":"Photo could not be found."}
根据您的第一个建议,如果Exception包含状态和API错误代码,那么开发人员可以更好地了解他需要做什么以及在exception处理方面有更多选择。 如果exception还包含返回的错误消息,那么开发人员甚至不需要引用该文档。
- Transport.send(消息)在以下代码中不起作用.. netbeans卡在运行部分。 它不会继续下去……它永远挂在那里
- MongoDB Java API:com.mongodb.DBCollection.Save()和com.mongodb.DBCollection.Insert()之间的区别?
- Java中的一个好的HTML对象模型?
- 有限的SortedSet
- 是否可以从Java调用COM API?
- 不使用Java Mail API接收邮件正文/内容
- 如何将Java API的内部结构隐藏到世界其他地方
- 匿名上传File对象到Imgur API(JSON)给出了身份validation错误401
- 寻找一个简单的Java API来创建图形(边缘+节点)