自定义retrofit框架(三)使用编译时注解处理请求

2018-07-26  本文已影响303人  好多个胖子

前言

自定义retrofit框架(二)编写基本框架模型中,使用动态代理+反射的方式处理注解,实现了和retrofit一样的网络请求方式。这篇文章将采用java-apt(编译时注解处理器)来处理注解(编译时注解技术在EventBus,Butterknife,ARounter等框架中均有使用),这样做可以避免在代码中使用反射。当然在实际的网络请求中,网络延迟时长远比反射处理时长高出许多倍,这篇文章重在学习编译时注解操作。

效果展示

首先来看一下我们需要的效果

  1. 定义接口 (为了缩短篇幅,这里接口定义只定义一个方法)
@ApiService
public interface PhpService {

    @LRequest("login")
    Observable<HttpResult<UserInfo>> login(
                        @Param("name") String name, 
                        @Param("password") String password);
}
  1. 通过菜单栏Build(在AndroidStudio中点击build -> make project)生成实现类,然后即可使用
 PhpService service =  new PhpServiceImpl("basr-url");//入参填写域名
       service.login("luqihua","123456")
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .subscribe(new Consumer<HttpResult<UserInfo>>() {
                   @Override
                   public void accept(HttpResult<UserInfo> userInfoHttpResult) throws Exception {
                       
                   }
               });

以上就是改造好的库的使用方式。看上去也是很简单的,我们只编写了一个类似retrofit接口请求类,便可以使用它的实现类来完成具体的请求,new出来的实现类可以使用单例模式包装。在app/build/generated/source/apt/debug路径下可以查看到我们生成的实现类如下:

public class PhpServiceImpl implements PhpService {
  private final String _baseUrl;

  private HttpRequest _request = new HttpRequest();

  public PhpServiceImpl(final String baseUrl) {
    this._baseUrl=baseUrl;
  }

  @Override
  public Observable<HttpResult<UserInfo>> login(String name, String password) {
    String _url = "login";
    if(_url.length()==0) {
      throw new RuntimeException("incorrect url");
    }
    if(!_url.startsWith("http")) {
      _url = _baseUrl+_url;
    }
    final Type _type = new TypeToken<HttpResult<UserInfo>>(){}.getType();
    final Map<String,String> _params = new HashMap<>();
    final Map<String,String> _headers = new HashMap<>();
    _params.put("name",name);
    _params.put("password",password);
    return (Observable<HttpResult<UserInfo>>)_request.form(_url,LMethod.POST,_headers,_params,_type);
  }
}

可以看到生成的实现类中有一个baseUrl,这个是域名。另一个全局变量HttpRequest,这是网络请求的包装类,用来具体构建网络请求,从而在接口实现类中做简单的入参即可,减少代码生成量。HttpRequest 的代码如下:

@RequestWrapper
public class HttpRequest implements IRequestWrapper<Observable<?>> {
    private static Gson sGson = new Gson();

    @Override
    public Observable<?> form(final String url,
                              final Enum<LMethod> method,
                              Map<String, String> headers,
                              Map<String, String> params,
                              final Type type) {
        return new FormRequest()
                .url(url)
                .params(params)
                .headers(headers)
                .method(method)
                .observerResponseBody()
                .map(new Function<ResponseBody, Object>() {
                    @Override
                    public Object apply(ResponseBody responseBody) throws Exception {
                        Object o = sGson.fromJson(responseBody.string(), type);
                        return o;
                    }
                });
    }

}

代码编写

在阅读这部分代码之前需要掌握以下基础

  1. 注解编写
//注解的编写这里只介绍一个LRequest,其余的在源码中可查看。注意把Retention设置为SOURCE
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface LRequest {
    String value();//访问路径

    LMethod method() default LMethod.POST;//访问方法  post或者get

    ContextType type() default ContextType.FORM;//访问类型,form、json、multipart
}
  1. 注解处理器编写
    implementation 'com.google.auto:auto-common:0.10'
    implementation 'com.google.auto.service:auto-service:1.0-rc4'
    implementation 'com.squareup:javapoet:1.11.1'
    implementation project(':lib-annotation')
public class MethodCreate {

    /**
     * 根据方法元素生成每个接口方法公共的代码
     *
     * @param executableElement
     * @return
     */

    public MethodSpec createMethod(ExecutableElement executableElement) {
        LRequest LRequest = executableElement.getAnnotation(LRequest.class);
        //返回值类型
        TypeMirror returnType = executableElement.getReturnType();
        //因为返回值类型是形如 Observable<HttpResult<UserInfo>> ,因此我们需要得到HttpResult<UserInfo>
        if (returnType instanceof DeclaredType) {
            DeclaredType type = (DeclaredType) returnType;
            returnType = type.getTypeArguments().get(0);
        }

        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(executableElement.getSimpleName().toString())
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .returns(ClassName.get(executableElement.getReturnType()));
        // 处理url
        String url = LRequest.value();
        methodBuilder.addStatement("String _url = $S", url)
                .beginControlFlow("if(_url.length()==0)")
                .addStatement("throw new $T(\"incorrect url\")", RuntimeException.class)
                .endControlFlow()
                .beginControlFlow("if(!_url.startsWith(\"http\"))")
                .addStatement("_url = _baseUrl+_url")
                .endControlFlow();


        // 生成代码 final Type _type = new TypeToken<returnType>(){}.getType();
        methodBuilder.addStatement("final $T _type = new $T<$T>(){}.getType()", Type.class,
                ClassName.get("com.google.gson.reflect", "TypeToken")
                , ClassName.get(returnType));

        // 生成代码 Map<String,String> _params = new HashMap<>();
        methodBuilder.addStatement("final $T<String,String> _params = new $T<>()", Map.class, HashMap.class);

        // 生成代码 Map<String,String> _headers = new HashMap<>();
        methodBuilder.addStatement("final $T<String,String> _headers = new $T<>()", Map.class, HashMap.class);

        //如果是MULTIPART  则添加一个Map<String,File> _fileMap = new HashMap<>();用于存放文件数据
        if (LRequest.type() == ContextType.MULTIPART) {
            methodBuilder.addStatement("final $T<String,$T> _fileMap = new $T<>()", Map.class, File.class, HashMap.class);
        }

        if (LRequest.type() == ContextType.JSON) {
            methodBuilder.addStatement("$T _jsonBody=null", Object.class);
        }

        //解析方法的形参
        for (VariableElement variableElement : executableElement.getParameters()) {
            //形参的名称
            final String parameterName = variableElement.getSimpleName().toString();
            //形参传入实现类的方法
            methodBuilder.addParameter(ClassName.get(variableElement.asType()), parameterName);

            Header header = variableElement.getAnnotation(Header.class);
            //将头信息放入_headers
            if (header != null) {
                methodBuilder.addStatement("_headers.put($S,$L)", header.value(), parameterName);
                continue;
            }

            //将单个的参数键值对放入_params
            Param param = variableElement.getAnnotation(Param.class);
            if (param != null) {
                methodBuilder.addStatement("_params.put($S,$L)", param.value(), parameterName);
                continue;
            }

            //将参数集合放入_params
            ParamMap paramMap = variableElement.getAnnotation(ParamMap.class);
            if (paramMap != null) {
                methodBuilder.addStatement("_params.putAll($L)", variableElement.getSimpleName().toString());
                continue;
            }
            //如果是上传文件的   处理文件参数
            if (LRequest.type() == ContextType.MULTIPART) {
                FileParam fileParam = variableElement.getAnnotation(FileParam.class);
                if (fileParam != null) {
                    methodBuilder.addStatement("_fileMap.put($S,$L)", fileParam.value(), parameterName);
                }

                FileMap fileMap = variableElement.getAnnotation(FileMap.class);
                if (fileMap != null) {
                    methodBuilder.addStatement("_fileMap.putAll($L)", parameterName);
                }

                ProgressListener listener = variableElement.getAnnotation(ProgressListener.class);
                if (listener != null) {
                    methodBuilder.addStatement("final $T _listener = $L",
                            ClassName.get("com.lu.http.Interface", "IProgressListener"),
                            parameterName);
                }
            } else if (LRequest.type() == ContextType.JSON) {

                Body jsonBody = variableElement.getAnnotation(Body.class);
                if (jsonBody != null) {
                    methodBuilder.addStatement("_jsonBody= $L", parameterName);
                }
            }
        }

        final String targetMethod = LRequest.type().name().toLowerCase();

        if (LRequest.type() == ContextType.MULTIPART) {
            methodBuilder.addStatement("return ($T)_request.$L(_url,_headers,_params,_fileMap,_listener,_type)",
                    executableElement.getReturnType(),
                    targetMethod);
        } else if (LRequest.type() == ContextType.FORM) {
            methodBuilder.addStatement("return ($T)_request.$L(_url,$T.$L,_headers,_params,_type)",
                    executableElement.getReturnType(),
                    targetMethod,
                    LMethod.class,
                    LRequest.method());
        } else if (LRequest.type() == ContextType.JSON) {
            methodBuilder.addStatement("return ($T)_request.$L(_url,_headers,_params,_jsonBody,_type)",
                    executableElement.getReturnType(),
                    targetMethod);
        }

        return methodBuilder.build();
    }
}

至此我们的代码编写完毕,即可运行测试

源码地址

上一篇 下一篇

猜你喜欢

热点阅读