Retrofit如何实现自定义注解?

Retrofit现在在网络请求中是使用的最多的库,它是对OkHttp的一层封装,使用起来非常方便。但是在工作中经常会遇到一些需求,比如对某些接口加上一些token验证,某一些不需要加上token验证,我们当然可以在每一个接口后面直接加上获取不加这个参数,但是这样做不是好的解决方案。那么这个时候我们就需要考虑使用自定义注解的方式来解决这个问题。

首先实现一个简单的网络请求

我们使用wanandroid中的两个接口用来做演示,wanandroid地址是https://www.wanandroid.com/

用到的两个接口如下:

https://www.wanandroid.com/banner/json
https://www.wanandroid.com/friend/json

首先定义了一个Bean类和接口类WanAndroidApi, 如下:

data class Bean(val errorCode: Int,
                val errorMsg: String,
                val data: List<*>)

public interface WanAndroidApi {

    @GET("banner/json")
    Observable<Bean> getBanner();

    @GET("friend/json")
    Observable<Bean> getFriend();
}

然后我们直接在MainActivity中使用这个接口(记得加上网络请求权限),看看效果如何:

@SuppressLint("CheckResult")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .addInterceptor(new TokenInterceptor())
                .addInterceptor(new LogInterceptor())
                .build();

        Retrofit retrofit = new Retrofit.Builder()
                .client(client)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl("https://www.wanandroid.com/")
                .build();

        retrofit.create(WanAndroidApi.class).getBanner()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Bean>() {
                    @Override
                    public void accept(Bean bannerBean) {
                        Log.i("oman", "getBanner data: " + bannerBean);
                    }
                });
    }

运行后打印的结果比较多,就粘贴一点:
在这里插入图片描述

OK, 到这里简单的请求就实现了,是不是很简单,但是关于本篇的需求还是一点没开始呢?


自定义Token

首先自定义一个Token注解, 需要传递一个参数,缺省是true代表需要传递token参数,

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Token {
    boolean value() default true;
}

那么假如现在有这么一个需求,就是第一个接口banner.json不需要加token参数,而friend.json需要加token参数的话,我们就需要修改我们的接口类,对接口方法加上注解, 如下:

public interface WanAndroidApi {

    @Token(value = false)
    @GET("banner/json")
    Observable<Bean> getBanner();

    @Token
    @GET("friend/json")
    Observable<Bean> getFriend();
}

加上了注解后怎么使用呢?这里就需要思考一下了,我们想要获取方法上面的注解,其实方法有好几种,比如自定义CallAdapter.Factory,从参数中获取注解信息。或者使用双重动态代理的思想。这里我们就使用双重动态代理的思想来实现这个功能。

首先我们声明一个枚举单例(枚举单例不了解的可以点击枚举单例查看),用来保存需要添加token的接口:

public enum TokenSets {
    INSTANCE;
    public Set<String> tokenUrls = new CopyOnWriteArraySet<>();

    public void add(String url) {
        tokenUrls.add(url);
    }

    public boolean contains(String url) {
        return tokenUrls.contains(url);
    }
}

然后修改我们的代理代码块如下,这里为了方便看代码,并没有做封装,真实项目中最好封装一下:

   WanAndroidApi api = retrofit.create(WanAndroidApi.class);
   wanAndroidApi = (WanAndroidApi) Proxy.newProxyInstance(this.getClassLoader(), new Class[]{WanAndroidApi.class}, new InvocationHandler() {
       @RequiresApi(api = Build.VERSION_CODES.N)
       @Override
       public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
           Token tokenAnnotation = method.getDeclaredAnnotation(Token.class);
           boolean needToken = tokenAnnotation != null && tokenAnnotation.value();
           String url = null;
           for (Annotation annotation : method.getDeclaredAnnotations()) {
               if (annotation instanceof GET) {
                   url = ((GET) annotation).value();
               } else if (annotation instanceof POST) {
                   url = ((POST) annotation).value();
               }
               //TODO 处理其它的请求类型,这里只是演示,只处理了GET POST请求
           }
           if (needToken)
               TokenSets.INSTANCE.add(BASE_URL + url);
           return method.invoke(api, objects);
       }
   });

上面的代码就实现了双重代理,在方法调用的时候,我们可以保存需要添加token的接口,保存起来。

然后就用到了OkHttp的伟大的拦截器了,拦截器的作用主要就是为了拦截我们的请求,做出一些需求上的参数改变,代码如下, 这里仅仅是模拟添加一个假的token:

public class TokenInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        String url = chain.request().url().toString();
        if (TokenSets.INSTANCE.contains(url)) {
            Log.i("aaa", "intercept need add token: " + url);
            return chain.proceed(chain.request().newBuilder().url(
                    chain.request().url().newBuilder().addQueryParameter("token", "asdf").build()).build());
        } else {
            return chain.proceed(chain.request());
        }
    }
}

好了,准备工作做好了,现在运行一下吧,看看结果:
在这里插入图片描述
很明显,上面已经实现了我们的需求,仅仅对需要添加token的接口添加了token。这样以后需要添加token的接口直接注解上Token就可以了,是不是非常方便?而且如果想向header中添加以下参数的话,只需要在拦截器中添加到header中即可,原理明白了,怎么使用就随意了。

下面粘上整个MainActivity类的代码, xml文件中其实仅仅只有两个按钮而已,点击分别获取两个接口的数据。

public class MainActivity extends AppCompatActivity {

    WanAndroidApi wanAndroidApi;

    private static final String BASE_URL = "https://www.wanandroid.com/";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .addInterceptor(new TokenInterceptor())
                .addInterceptor(new LogInterceptor())
                .build();

        Retrofit retrofit = new Retrofit.Builder()
                .client(client)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl("https://www.wanandroid.com/")
                .build();

        WanAndroidApi api = retrofit.create(WanAndroidApi.class);

        wanAndroidApi = (WanAndroidApi) Proxy.newProxyInstance(this.getClassLoader(), new Class[]{WanAndroidApi.class}, new InvocationHandler() {
            @RequiresApi(api = Build.VERSION_CODES.N)
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                Token tokenAnnotation = method.getDeclaredAnnotation(Token.class);
                boolean needToken = tokenAnnotation != null && tokenAnnotation.value();
                String url = null;
                for (Annotation annotation : method.getDeclaredAnnotations()) {
                    if (annotation instanceof GET) {
                        url = ((GET) annotation).value();
                    } else if (annotation instanceof POST) {
                        url = ((POST) annotation).value();
                    }
                    //TODO 处理其它的请求类型,这里只是演示,只处理了GET POST请求
                }
                if (needToken)
                    TokenSets.INSTANCE.add(BASE_URL + url);
                return method.invoke(api, objects);
            }
        });
    }

    @SuppressLint("CheckResult")
    public void apiNotContainToken(View view) {
        wanAndroidApi.getBanner()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Bean>() {
                    @Override
                    public void accept(Bean bannerBean) {
                        Log.i("oman", "getBanner data: " + bannerBean.toString());
                    }
                });
    }

    @SuppressLint("CheckResult")
    public void apiContainToken(View view) {
        wanAndroidApi.getFriend()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Bean>() {
                    @Override
                    public void accept(Bean friendBean) {
                        Log.i("oman", "getFriend data: " + friendBean);
                    }
                });
    }
}

源码的地址在github中,点击Android进阶指南查看,里面还有一些常见的面试题和对应的答案,后面会不停的丰富这个项目,喜欢的话就star支持一下。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
1. 在定义数据持久层的GenericDAO、GenericDAOImpl等过程中,常见的自定义注解有: - @Repository:标注数据访问组件,表示这个类是一个DAO,用于访问数据库或其他数据源。 - @Transactional:标注方法或类,表示这个方法或类需要事务管理。通常用在Service层,表示这个Service中的所有方法都需要事务管理。 - @Autowired:标注在属性、构造器或Setter方法上,表示这个属性需要自动注入一个Bean。 这些注解分别起到如下作用: - @Repository:声明这个类是一个DAO,方便Spring容器自动扫描并管理。 - @Transactional:声明这个方法或类需要事务管理,方便Spring容器自动配置事务管理器,并将方法或类纳入事务管理。 - @Autowired:声明这个属性需要自动注入一个Bean,方便Spring容器自动注入依赖的Bean。 2. 建造者设计模式一般应用于创建复杂对象,通过将一个复杂对象的构建过程分解为多个简单对象的构建过程,并将这些简单对象按照一定的顺序组合起来,最终构建出复杂对象。在Java中,建造者设计模式体现在如下几个方面: - StringBuilder:StringBuilder类通过append()方法将多个字符串拼接起来,最终构建出一个复杂的字符串对象。 - AlertDialog.Builder:AlertDialog.Builder类通过链式调用多个方法,分别设置对话框的标题、消息、按钮等属性,最终构建出一个AlertDialog对象。 - Retrofit.Builder:Retrofit.Builder类通过链式调用多个方法,分别设置网络请求的URL、请求方法、请求参数等属性,最终构建出一个Retrofit对象。 具体实现方式可以参考如下代码: ``` public class Person { private String name; private int age; private String gender; private Person(Builder builder) { this.name = builder.name; this.age = builder.age; this.gender = builder.gender; } public static class Builder { private String name; private int age; private String gender; public Builder name(String name) { this.name = name; return this; } public Builder age(int age) { this.age = age; return this; } public Builder gender(String gender) { this.gender = gender; return this; } public Person build() { return new Person(this); } } } ``` 3. 性能优化的思想一般应用于代码的编写和系统的设计过程中。具体体现在如下几个方面: - 数据库优化:通过优化SQL语句、创建索引、调整数据表结构等方式,提升数据库读写性能。 - 垃圾回收优化:通过调整JVM参数、减少对象的创建、优化代码设计等方式,提升JVM的垃圾回收性能。 - 线程池优化:通过合理配置线程池参数、控制线程的数量、优化代码设计等方式,提升线程池的效率和性能。 - 缓存优化:通过使用缓存技术、合理配置缓存策略、优化缓存数据结构等方式,提升应用程序的性能。 在具体的项目中,可以根据实际情况选择相应的优化方式。例如,在Web应用中,可以通过使用CDN、压缩静态资源、使用分布式缓存等方式,提升Web应用的性能。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值