【问题标题】:REST Methods - Executing a "common" method call before other methodsREST 方法 - 在其他方法之前执行“通用”方法调用
【发布时间】:2017-04-03 10:23:25
【问题描述】:

我有一个调用REST API 的安卓应用。 REST API 由JWT 保护,当用户登录应用程序时,我获得了 android 的令牌。令牌将在 60 分钟后过期。

在我的 android 应用程序中,我有不同的类,它们总共包含 50-60 个 REST 调用。 android 应用程序使用Retrofit 连接到这些 REST 方法。我有一些方法需要在执行另一个之后才能工作,这些方法在第一个方法的onResponse 方法中。

我编写了一个名为Token 的特殊类,其中setget 来自变量的JWT 令牌。每个 REST 调用都从此类获取令牌。

无论如何,由于 REST 使用 JWT 保护,我必须在 50 分钟后重新更新令牌。我首先要检查Token 类中的token 变量是否即将过期,它有一个特殊的方法whenWillExpire() 可以告诉我令牌何时过期。如果是,则再次调用 REST API 并获取新令牌。

情况是,因为我不知道在哪个 REST 调用上我必须重新更新令牌,所以我必须在 any REST 调用之前执行此令牌过期检查并在之前获取新令牌(如果过期)调用选定的 REST 方法。举个例子,假设我有一个名为 renew() 的方法,它检查令牌并通过执行 REST 工作从服务器获取它。 renew() 的执行完成后,任何其他 REST 调用都应该运行。 renew() 应该首先在每个 REST 调用请求上运行。这是此方法的一个示例。

private void renew(String token) {

if(token expired){
    Gson gson = new GsonBuilder()
            .setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
            .create();

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(RestCommon.URL)
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build();

GetNewToken endPoint = retrofit.create(GetNewToken.class);
Call<ResponseBody> call = endPoint.getNewToken();
call.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Response<ResponseBody> response, Retrofit retrofit) {
         // Good. Now we have the token. Call other methods here.
      }

    @Override
    public void onFailure(Throwable t) {
        t.printStackTrace();
    }
}); 
}
   else
     {
       //Not expired. Call other methods.
     }
   }

那么,在执行请求的方法之前,如何确保 renew() 在每个 REST 请求中运行?最好的设计是什么?我有 50-60 种 REST 方法,用不同的名称复制上面的代码并在其中添加其他 REST 调用绝对不是一个好的模式。

【问题讨论】:

  • 这是一个明智的设计,我通常将所有 API 调用放在一个 interface 中,并且我有一个类可以创建 Retrofit retrofit ... 的 1 个实例(单例)并在所有调用中使用它,并且,通常我创建一个类(包装器),该类在interface 中为每个API 端点提供一个方法,在该方法中我调用使用retrofit 实例来调用API,我将callback 作为匿名内部类传递给方法,它可能看起来很复杂,如果你愿意,我可以发布一个答案。关键在于包装方法调用,你可以做所有你想做的检查,包括令牌过期、续订调用......等等
  • 谢谢,请回复。但我也有“必须”一个接一个运行的 REST 调用。有时这个链有 6-7 个方法长。这也支持?
  • 是的,您将在onResponse() onFail() 中将匿名内部类作为CallBack 传递,您可以做任何您想做的事情。我会在几分钟内给你看一个样本
  • 实际上我到了令牌需要刷新的时候,建议的设计支持这一点,但是一旦刷新令牌,就无法找到一种干净的方式来恢复原始请求,不确定您是否仍然感兴趣在答案中..?
  • @Yazan:实际上刷新的意思是生成一个新密钥。这只是另一个 REST 调用

标签: java android rest asynchronous retrofit


【解决方案1】:

定义所有 API 端点:

public interface MyApi {
    @GET("item/{id}")
    Call<Item> getItemById(@Path("id") String id, @Header("Authorization") String auth)

    @DELETE("item/{id}")
    Call<String> deleteItemById(@Path("id") String id, @Header("Authorization") String auth)

    @POST("auth")
    Call<String> getNewToken(@Field("token") String currentToken);// or other params needed by getNewToken

    //...etc
}

包装类:

public class MyApiManager{ //?? chose other name if u want :)

    private MyApi myApi = null;
    private static MyApiManager instance = null;

    public MyApiManager getInstance(){
        if(instance == null){
            instance = new MyApiManager();
        }
        return instance;
    }

    private MyApiManager(){
        Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.host.name")
        //other setting before build() ...
        .build();

        myApi = retrofit.create(MyApi.class);
    }

    //-------- General methods, like check token expired...

    private boolean checkAndGetNewToken(){
        if(token expired){
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    //note here using myApi directly to avoid recursive calls for checkAndGetNewToken()
                    Response<String> response = myApi.getNewToken(token).execute();
                    Token.set(response);//update your token
                }//run()
            });
            t.start();
            t.join();
        }//token exp
        return true;
    }//checkToken

    //------ wrapper methods for each API endpoint

    public Call<Item> getItemById(String id) throws TokenExpiredException{
        checkTokenExpiration();
        return myApi.getItemById(id, "Authorization:Bearer "+Token.get());
    }

    public Call<Item> deleteItemById(String id) throws TokenExpiredException{
        checkTokenExpiration();
        return myApi.deleteItemById(id, "Authorization:Bearer "+Token.get());
    }

    public Call<Item> getNewToken(String currentToken) throws TokenExpiredException{
        checkTokenExpiration();
        return myApi.getNewToken(currentToken);
    }
}

使用示例:

public class SomeClass {
    public void someMethod(){
        MyApiManager.getInstance().getItem("102").enqueue(new Callback<Item>() {
            @Override
            public void onResponse(Response<Item> response, Retrofit retrofit) {
                callApiStageB(response.getBody().getId()); //?
            }

            @Override
            public void onFailure(Throwable t) {
                t.printStackTrace();
                //show a toast ...
            }
        });
    }

    public void callApiStageB(String id){
        MyApiManager.getInstance().deleteItemById(id).enqueue(new Callback<String>() {
            @Override
            public void onResponse(Response<String> response, Retrofit retrofit) {
                //....more nested api-calls, or do anything else
            }

            @Override
            public void onFailure(Throwable t) {
                t.printStackTrace();
                //show a toast ...
            }
        });
    }
}

如您所见,每次使用 apiManager 调用都会首先检查/请求新令牌,问题是当需要新令牌时,这部分已处理,但是如何返回并执行原始请求(例如 getItem() ) 这一步做完之后,因为应该是通过enqueue()和一个CallBack做的

我为此建议的 sol(Thread 和 join())很脏,我认为它会抛出一些异常,例如 NetworkOnMainThread... 希望这对您有任何帮助:)

【讨论】:

    猜你喜欢
    • 2018-08-26
    • 2017-08-29
    • 1970-01-01
    • 1970-01-01
    • 2022-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多