RxJava+Retrofit实现全局过期token自动刷新的实践
2016-12-06 10:46
696 查看
最近项目中遇到一个需求,token过期的自动处理。我们项目中网络请求模块使用了rxJava+Retrofit的组合,相信看到这个文章的人都对此比较了解了,不多赘述。
看到token过期处理首先想到的是在请求回调中获取到token过期的信息,然后进行刷新操作,但是由于项目中使用到的网络请求接口众多,而且大多数接口都有可能会出现token过期的情况,如此处理就显得比较麻烦了。
百度之,发现了使用代理来处理的方法,见大神博客:http://alighters.com/blog/2016/08/22/rxjava-plus-retrofitshi-xian-zhi-demo/?utm_source=tuicool&utm_medium=referral ,本篇文章主要讲一讲我在实践中遇到的问题以及解决的方案。
Observale 的 retryWhen 的方法,识别 token 过期失效的错误信息,此时发出刷新 token 请求的代码块,完成之后更新 token,这时之前的请求会重新执行,但将它的 token 更新为最新的。另外通过代理类对所有的请求都进行处理,完成之后,我们只需关注单个 API 的实现,而不用每个都考虑 token 过期,大大地实现解耦操作。
GsonConverterFactory //在Retrofit.builder().addConverterFactory中使用,直接拷贝原有的 retrofit2.converter.gson.GsonConverterFactory到项目目录下即可
GsonRequestBodyConverter //同上,和retrofit中的一样
GsonResponseBodyConverter //主要的过期异常抛出代码在其convert()方法中
ProxyHandler //代理类,token刷新代码都在这个类中
alighters在其demo对 GsonConverterFactory类中的responseBodyConverter方法中的Type做了包装,其包装代码如下:
而在实践中发现这样子包装会造成一些问题,本着尽量少改源码的思想,我这里没有做这些包装,维持原样,即:
接下来就到了真正抛出token_timeout异常的地方了,在GsonResponseBodyConverter中,直接看代码:
refreshTokenWhenTokenInvalid代码:
updateMethodToken方法:
至此全局的token自动刷新功能就完成了,只需用Proxy.newProxyIntance生成的ApiService类来调用请求接口即可实现token过期自动刷新了。为测试可以使用Okhttp的Intercepter来拦截请求,使用Mock的数据来测试,不过要注意mock的数据如果responseCode一直是token_timeout会导致一直循环retry,可以添加 刷新token次数限制来避免。
后记:做完token刷新,本地测试ok之后 兴冲冲的使用服务端的数据来测试,结果居然失败了,罪魁祸首是什么?请看下集,gson解析同一位置不同类型json数据。
看到token过期处理首先想到的是在请求回调中获取到token过期的信息,然后进行刷新操作,但是由于项目中使用到的网络请求接口众多,而且大多数接口都有可能会出现token过期的情况,如此处理就显得比较麻烦了。
百度之,发现了使用代理来处理的方法,见大神博客:http://alighters.com/blog/2016/08/22/rxjava-plus-retrofitshi-xian-zhi-demo/?utm_source=tuicool&utm_medium=referral ,本篇文章主要讲一讲我在实践中遇到的问题以及解决的方案。
Token自动刷新实现
1.实现思想
与alighters的思想相同,摘录如下:利用Observale 的 retryWhen 的方法,识别 token 过期失效的错误信息,此时发出刷新 token 请求的代码块,完成之后更新 token,这时之前的请求会重新执行,但将它的 token 更新为最新的。另外通过代理类对所有的请求都进行处理,完成之后,我们只需关注单个 API 的实现,而不用每个都考虑 token 过期,大大地实现解耦操作。
2.token过期处理相关类
token过期处理主要用到如下4个类GsonConverterFactory //在Retrofit.builder().addConverterFactory中使用,直接拷贝原有的 retrofit2.converter.gson.GsonConverterFactory到项目目录下即可
GsonRequestBodyConverter //同上,和retrofit中的一样
GsonResponseBodyConverter //主要的过期异常抛出代码在其convert()方法中
ProxyHandler //代理类,token刷新代码都在这个类中
3.错误抛出
首先提及我们项目中统一Response的model封装:public class MResponse<T> implements Serializable { public String responseCode;//结果码 public T responseData; public String responseToken; }当正确返回数据时,responseCode为success,当出现token过期时 responseCode为token_timeout.
alighters在其demo对 GsonConverterFactory类中的responseBodyConverter方法中的Type做了包装,其包装代码如下:
public Converter<ResponseBody, ?> responseBodyConverter(final Type type, Annotation[] annotations, Retrofit retrofit) { Type newType = new ParameterizedType() { @Override public Type[] getActualTypeArguments() { return new Type[] { type }; } @Override public Type getOwnerType() { return null; } @Override public Type getRawType() { return ApiModel.class; } }; TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(newType)); return new GsonResponseBodyConverter<>(adapter); }
而在实践中发现这样子包装会造成一些问题,本着尽量少改源码的思想,我这里没有做这些包装,维持原样,即:
public Converter<ResponseBody, ?> responseBodyConverter(final Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); return new GsonResponseBodyConverter<>(gson, adapter); }这样也避免了Type的类型锁死为我们的Model封装。
接下来就到了真正抛出token_timeout异常的地方了,在GsonResponseBodyConverter中,直接看代码:
public Object convert(ResponseBody value) throws IOException { try { JsonReader jsonReader = gson.newJsonReader(value.charStream()); Object obj = adapter.read(jsonReader); if (obj instanceof MResponse) {//token过期则抛出异常 if (((MResponse) obj).responseCode.equals("token_timeout")) { throw new IllegalArgumentException("token_timeout");//异常类型可以自己定义 } } return obj; } finally { value.close(); } }使用instanceof使得响应的Model封装不再固定为MResponse,如果有其他类型可以加上else if ,扩展性更好一些。
4.添加代理
添加代理的方式请参照alighters的文章,这里不再描述,贴上一段ProxyHandler中invoke()方法的代码:public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { final InvokeArguments invokeArguments = new InvokeArguments();//变量集合类,避免使用ProxyHandler的成员变量。 return Observable.just(null).flatMap(new Func1<Object, Observable<?>>() { @Override public Observable<?> call(Object o) { try { try { if (invokeArguments.isTokenNeedRefresh) {//需要更新的时候更新方法里的token参数 updateMethodToken(invokeArguments, args); } return (Observable<?>) method.invoke(mProxyObject, args); } catch (InvocationTargetException e) { e.printStackTrace(); } } catch (IllegalAccessException e) { e.printStackTrace(); } return null; 4000 } }).retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() { @Override public Observable<?> call(Observable<? extends Throwable> observable) { return observable.flatMap(new Func1<Throwable, Observable<?>>() { @Override public Observable<?> call(Throwable throwable) { if (throwable instanceof IllegalArgumentException && throwable.getMessage().equals("token_timeout")) {//可自定义过期异常 //token_过期处理 return refreshTokenWhenTokenInvalid(invokeArguments);//获取新的token } return Observable.error(throwable);//使用error避免进入死循环 } }); } }); }
refreshTokenWhenTokenInvalid代码:
private Observable<?> refreshTokenWhenTokenInvalid(final InvokeArguments invokeArguments) { synchronized (ProxyHandler.class) { invokeArguments.refreshTokenError = null; // call the refresh token api. ApiWrapper.getInstance().refreshToken() .subscribe(new Subsciber<MResponse>() { @Override public void onNext(MResponse model) { if (model != null) { invokeArguments.isTokenNeedRefresh = true; tokenChangedTime = new Date().getTime(); GlobalToken.updateToken(model.responseToken);//此处使用自己的token存放方式 System.out.println("Refresh token success, time = " + tokenChangedTime); } } @Override public void onError(Throwable e) { invokeArguments.refreshTokenError = e; } }); if (invokeArguments.refreshTokenError != null) { return Observable.error(invokeArguments.refreshTokenError); } else { return Observable.just(true); } } }
updateMethodToken方法:
private void updateMethodToken(InvokeArguments invokeArguments, Object[] args) { if (invokeArguments.isTokenNeedRefresh && !TextUtils.isEmpty(your new token)) { for (int i = 0, len = args.length; i < len; i++) { if (args[i] instanceof RequestParams) { ((RequestParams) args[i]).requestToken = your new token; } } invokeArguments.isTokenNeedRefresh = false; } }
至此全局的token自动刷新功能就完成了,只需用Proxy.newProxyIntance生成的ApiService类来调用请求接口即可实现token过期自动刷新了。为测试可以使用Okhttp的Intercepter来拦截请求,使用Mock的数据来测试,不过要注意mock的数据如果responseCode一直是token_timeout会导致一直循环retry,可以添加 刷新token次数限制来避免。
后记:做完token刷新,本地测试ok之后 兴冲冲的使用服务端的数据来测试,结果居然失败了,罪魁祸首是什么?请看下集,gson解析同一位置不同类型json数据。
相关文章推荐
- java泛型详解
- Maven依赖jar的自动升级配置
- 4000 svn检出与运行和新建java项目的首次运行
- Java 多线程 并发编程
- Spring.Net+Nhibernate
- 一、MyBatis简介与配置MyBatis+Spring+MySql
- spring framework体系结构及内部各模块jar之间的maven依赖关系
- spring batch(一):基础部分
- Java常用软件教程
- myeclipse和eclipse 项目中的Libraries是空的
- Java转换字符
- Java BIO、NIO、AIO(NIO.2) 区别
- java产生随机数
- java.net.ConnectException: Connection refused问题解决办法
- svn下载maven父子工程
- java学习之编译时类型(前期绑定)和运行时类型 (后期绑定、动态绑定、运行时绑定)
- Java访问控制权限
- 【leetcode】Ugly Number-----Java
- java之Pattern类详解
- 使用commons-beanutils迭代获取javabean的属性