动态代理和注解处理接口Token问题

背景

目前在做SDK时,网络层使用的框架是OkHttp + Retrofit,需要对外提供API方式的网络接口调用。

一般来说,后台都会有接口校验设计,需要在请求头包含部分加密参数进行验证。

我们的项目也是这样处理,外放的接口需要先调用登录接口获取Token,根据Token在请求头进行组合形成鉴权,才能成功通过后台的校验进行使用。

这时候想,如果每个接口都需要提前先获取Token,工作量非常大,于是想到了类似Java的AOP的面向切面处理方式,使用动态代理灵活在接口方法前插入获取Token逻辑。

而部分接口例如,获取验证码、登录相关的,并不需要Token,因此再自定义一个注解用于区别。

动态代理

要理解动态代理首先要理解代理,

  • 代理:给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。

代理有什么用呢?举个例子,上大学偶尔舍友会不想上他自己的课,于是让我去代签名,此时我就成为了这个舍友的代理,替他去上课。

一般来说,我们使用聚合方式让代理类持有一个被代理类对象,即可实现代理,而此种方式一般称为静态代理。

在项目中我们有一个Repository实现了具体的网络接口请求,里面有许多接口方法,如下所示:

1
2
3
4
5
6
public class Repository implements IRepository {
void method1() {...}
void method2() {...}
void method3() {...}
...
}

目前我们想要有一个Repository的Proxy类,在每个接口前加入获取Token的逻辑,如果用静态代理来做,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class RepositoryProxy implements IRepository {

IRepository repository;

void method1() {
getToken();
repository.method1();
}
void method2() {
getToken();
repository.method2();
}
void method3() {
getToken();
repository.method3();
}
...
void getToken() {...}
}

可以看到每个接口前都需要调用getToken,同样的逻辑需要反复实现,虽然效果达到了,但是由此写了很多重复的代码,因此静态代理也不适用于我们的需求。

于是使用动态代理来实现,这样就达到了预期的效果,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RepositoryInvocationHandler implements InvocationHandler {

IRepository repository;

@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
if(如果需要Token) {
getToken();
}
return method.invoke(repository, args);
}

void getToken() {...}
}

具体是实现InvocationHandler接口,覆写invoke方法,method.invoke(repository, args)即等价于调用IRepository的网络请求接口,在此方法前可以插入自定义逻辑。

在原本需要调用Repository的地方生成动态代理方式,如下:

1
2
3
Repository repository = new Repository();
RepositoryInvocationHandler repositoryInvocationHandler = new RepositoryInvocationHandler(repository);
proxy = (IRepository) (Proxy.newProxyInstance(IRepository.class.getClassLoader(), new Class[]{IRepository.class}, repositoryInvocationHandler));

注解

注解(也被成为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。 ———摘自《Thinking in Java》

简单来说注解的作用就是将我们的需要的数据储存起来,在以后的某一个时刻(可能是编译时,也可能是运行时)去调用它。

在我们的项目中,针对部分接口不需要Token的,处理逻辑不同,于是通过自定义注解实现,如下:

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedToken {
}

在Repository中,对需要获取Token的方法前加@NeedToken即可。最后修改InvocationHandler的逻辑,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class RepositoryInvocationHandler implements InvocationHandler {

IRepository repository;

@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
// 如果带NeedToken有注解,先判断是否获取了Token,否则需要获取,再调用API方法
NeedToken isNeedToken = method.getAnnotation(NeedToken.class);
if(isNeedToken != null) {
getToken();
}
// 否则直接调用方法即可
return method.invoke(repository, args);
}

void getToken() {...}
}

另外,由于getToken一般也为一个接口方法,需要异步回调获取Token,需注意RepositoryInvocationHandler类不能写成单例,应该每个接口方法对应一个proxy,对应一个获取Token回调,否则会造成冲突。

Done.

0%