前言
在自定义retrofit框架(二)编写基本框架模型中,使用动态代理+反射的方式处理注解,实现了和retrofit一样的网络请求方式。这篇文章将采用java-apt(编译时注解处理器)来处理注解(编译时注解技术在
EventBus
,Butterknife
,ARounter
等框架中均有使用),这样做可以避免在代码中使用反射。当然在实际的网络请求中,网络延迟时长远比反射处理时长高出许多倍,这篇文章重在学习编译时注解操作。
效果展示
首先来看一下我们需要的效果
- 定义接口 (为了缩短篇幅,这里接口定义只定义一个方法)
@ApiService
public interface PhpService {
@LRequest("login")
Observable<HttpResult<UserInfo>> login(
@Param("name") String name,
@Param("password") String password);
}
- 通过菜单栏
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;
}
});
}
}
代码编写
在阅读这部分代码之前需要掌握以下基础
- java-apt的实现之Element详解,可以理解一下在编译时注解中的元素概念
-
javapoet
的使用,这是一个帮助我们生成java源文件的开源库
- 注解编写
- 创建一个java工程
lib-annotation
,所有的注解都在该工程下
//注解的编写这里只介绍一个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
}
- 注解处理器编写
- 创建一个java工程,名为
lib-processor
- 引入需要的库
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')
-
创建一个类
HttpProcessor
集成自AbstractProcessor
,下面具体分析这个类的代码1.初始化类操作
private Elements mElementUtils; @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); mElementUtils = env.getElementUtils(); } @Override public Set<String> getSupportedAnnotationTypes() { //该方法添加所编写的注解,不过貌似这里面不添加的,在process方法中也可以获取处理 Set<String> types = new HashSet<>(); types.add(ApiService.class.getCanonicalName()); types.add(Param.class.getCanonicalName()); types.add(ParamMap.class.getCanonicalName()); types.add(RequestWrapper.class.getCanonicalName()); return types; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); }
- 核心的处理工作都在process方法中
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { //这一步我们获取所有带有ApiService标记的元素 Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(ApiService.class); //这一步骤判断元素的类型,必须是接口类型(实际上我们在定义ApiService的时候可以限制只能在Type上使用,但Type有可能是类和接口,因此这一步过滤掉类,只保留接口元素) for (Element element : elements) { if (element.getKind() != ElementKind.INTERFACE || !(element instanceof TypeElement)) continue; //生成接口实现类的具体操作 createSourceFile(roundEnv, element); } return true; }
-
createSourceFile
方法
从前面我们看到的接口实现类可以知道,
- 在接口实现类中应该有一个
_baseUrl
变量,有一个_request
变量 - 需要重写接口的方法
private void createSourceFile(RoundEnvironment roundEnv, Element element) { //新生成的类命名为接口名+Impl final String classImpName = element.getSimpleName().toString() + Constants.IMPL_CLASS_SUFFIX; //TypeSpec即代表一个类文件 TypeSpec.Builder builder = TypeSpec.classBuilder(classImpName) .addModifiers(Modifier.PUBLIC) //添加一个全局变量 String _baseUrl .addField(String.class, "_baseUrl", Modifier.FINAL, Modifier.PRIVATE) .addMethod(MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addParameter(String.class, "baseUrl", Modifier.FINAL) .addStatement("this._baseUrl=baseUrl") .build()) //全局变量HttpRequest,一个用于请求http的request,这个request是事先写好的 .addField(getRequestField(roundEnv)) .addSuperinterface(TypeName.get(element.asType())); //构建方法 MethodCreate methodCreate = new MethodCreate(); for (Element enclosedElement : element.getEnclosedElements()) { //因为我们只生成接口方法的重写方法,因此过滤掉不是方法的元素 if (!(enclosedElement instanceof ExecutableElement) || enclosedElement.getKind() != ElementKind.METHOD) { continue; } ExecutableElement executableElement = (ExecutableElement) enclosedElement; //过滤掉不带有LRequest注解的方法 if (executableElement.getAnnotation(LRequest.class) == null) { continue; } //生成方法 builder.addMethod(methodCreate.createMethod(executableElement)); } //生成java类 JavaFile javaFile = JavaFile.builder(mElementUtils.getPackageOf(element).toString(), builder.build()) .addFileComment(" This codes are generated automatically. Do not modify!") .build(); try { //写入文件 javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { e.printStackTrace(); } }
-
getRequestField
方法用于生成_request
变量
private FieldSpec getRequestField(RoundEnvironment roundEnv) { TypeName requestType = null; Set<? extends Element> requestElements = roundEnv.getElementsAnnotatedWith(RequestWrapper.class); if (requestElements.size() == 0) { requestType = Constants.HTTP_REQUEST; } else { for (Element element : requestElements) { requestType = ClassName.get(element.asType()); break; } } return FieldSpec.builder(requestType, "_request") .addModifiers(Modifier.PRIVATE) .initializer("new $T()", requestType) .build(); }
- MethodCreate类是用于根据方法元素生成方法重写的操作
-
MethodCreate
类
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();
}
}
至此我们的代码编写完毕,即可运行测试
网友评论