我们正在构建一个JavaSDK,以简化对我们提供REST API的服务之一的访问。这个SDK将由第三方开发者使用。我正在努力寻找最好的模式来实现更适合Java语言的SDK中的错误处理。
假设我们有其余的endpoint:GET /photos/{photoId}
。这可能会返回以下HTTP状态代码:
该服务看起来像这样:
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;
}
}
然而,我并不喜欢这个解决方案,主要有两个原因:
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;
这样,我可以在不破坏现有代码的情况下向方法的签名添加新的异常。然而,有了这个解决方案,开发人员不会被迫捕捉任何异常,也不会注意到他需要处理的错误情况,直到他仔细阅读留档(是的,没错!)或注意到方法签名中的异常。
在这种情况下,错误处理的最佳实践是什么?
除了我提到的那些,还有其他(更好的)选择吗?
我见过把你的建议2和3结合起来的图书馆,例如。
public Photo getPhoto(String photoID) throws RestServiceException, UnauthenticatedException, UnauthorizedException, NotFoundException;
这样,当您添加一个扩展了RestServiceException
的新的选中异常时,您不会更改该方法的约定,任何使用它的代码仍在编译。
与回调或未经检查的异常解决方案相比,它的一个优点是可以确保新错误将由客户端代码处理,即使它只是一个常规错误。在回调中,不会发生任何事情,如果出现未经检查的异常,客户端应用程序可能会崩溃。
看来你是靠“手”在做事。我建议你试试Apache CXF。
JAX-RS API是一个简洁的实现,它使您几乎忘记了REST。它与(也推荐)Spring玩得很好。
只需编写实现接口(API)的类。您需要做的是用JAX-RS注释对接口的方法和参数进行注释。
然后,CXF施展魔法。
在java代码中抛出正常异常,然后使用服务器/nd或客户端上的异常映射器在它们和HTTP状态代码之间进行转换。
这样,在服务器/Java客户端,您只处理常规的100%Java异常,CXF为您处理HTTP:您既有清晰的REST API的好处,也有Java客户端的好处随时可供您的用户使用。
客户机既可以从WDSL生成,也可以在运行时从接口注释的自省中构建。
见:
在我们的应用程序中,我们定义并映射了一组错误代码及其对应的异常:
它遵循REST和Java标准。
异常处理替代方案:回调
我不知道这是否是一个更好的选择,但你可以使用回调。通过提供默认实现,可以使某些方法成为可选的。看看这个:
/**
* 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必须从返回消息中执行判断,以确定