JavaPoet: 一个用来生成 java 源文件的 library
Jackson: 解析 jaon
前段时间在重写公司应用的网络请求部分,iOS 那边需要上 https,趁这个机会把所有的网络请求统一用 Retrofit 实现。不过几十个接口改起来可不容易,幸好接口的文档还算完善,自己写了一个小程序根据文档生成 Retrofit 接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| # 虚构的登陆接口 ## URL: /api_version/path/login ## Method: POST ## Request: | 参数名 | 参数类型 | 参数说明 | 是否必须 | 备注 | 示例 | | :-- | :-- | :-- | :-- | :-- | :-- | | username | String | 用户名 | 是 | | sunshine | | password | String | 密码 | 是 | | sunshine | ## Response: ```javascript { "res":"0", "data":"qweru9"//resetKey } ``` ## res code | res | 说明 | | --: | :-- | | 0 | 成功 | | 100 | 密码不正确 |
|
大概步骤就是读取接口相关的信息,再使用 java poet 拼接。URL 和 METHOD 直接获取就好。参数列表写了个正则提取出表格部分,然后比较暴力直接从第三行开始解析参数和类型,顺便做了一步和URL的比对,将路径参数使用@Path
注解标记。
方法签名部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public MethodSpec generate(ClassName result) { MethodSpec.Builder builder = MethodSpec.methodBuilder(apiName) .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) .returns(ParameterizedTypeName.get(ClassName.get(Observable.class), result));
params.stream() .filter(Param::filterToken) .forEach(param -> { ParameterSpec.Builder paramBuilder = ParameterSpec.builder(Utils.getType(param.type), param.name); AnnotationSpec.Builder queryBuilder = null;
if (isUrlParam(param.name)) { queryBuilder = AnnotationSpec.builder(Path.class); } else { switch (requestType) { case "GET": queryBuilder = AnnotationSpec.builder(Query.class); break; case "POST": queryBuilder = AnnotationSpec.builder(Field.class); break; } }
queryBuilder.addMember("value", "\"" + param.name + "\""); paramBuilder.addAnnotation(queryBuilder.build());
builder.addParameter(paramBuilder.build()); });
AnnotationSpec.Builder methodAnnotation = null; switch (requestType) { case "GET": methodAnnotation = AnnotationSpec.builder(GET.class); break; case "POST": builder.addAnnotation(FormUrlEncoded.class); methodAnnotation = AnnotationSpec.builder(POST.class); break; }
methodAnnotation.addMember("value", "\"" + url + "\"");
builder.addAnnotation(methodAnnotation.build());
return builder.build(); }
|
返回值的解析
返回值部分直接用 jackson 解析,递归判断下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| private TypeSpec buildJavaModel(JsonNode node, String name, String packageName) {
Builder jsonSpec = TypeSpec.classBuilder(name); jsonSpec.addModifiers(Modifier.PUBLIC);
node.fieldNames().forEachRemaining(fieldName -> { JsonNode item = node.get(fieldName);
if (fieldName.equals("res")) { jsonSpec.superclass(ClassName.get(PACKAGE_NAME, "Res")); } else { if (item.isArray() && item.get(0) != null) { ClassName list = ClassName.get("java.util", "List"); JsonNode listItem = item.get(0); TypeSpec spec = buildJavaModel(listItem, Utils.upperFirstChar(name) + "Item", packageName); TypeName listOfValue = ParameterizedTypeName.get(list, ClassName.get("", spec.name)); jsonSpec.addField(listOfValue, fieldName, Modifier.PUBLIC); } else if (item.isObject()) { TypeSpec typeSpec = buildJavaModel(item, name + Utils.upperFirstChar(fieldName), packageName); jsonSpec.addField(ClassName.get(PACKAGE_NAME, typeSpec.name), fieldName, Modifier.PUBLIC); } else { jsonSpec.addField(Utils.getType(item), fieldName, Modifier.PUBLIC); } } });
TypeSpec v = jsonSpec.build(); JavaFile javaFile = JavaFile.builder(packageName, v) .build();
result = ClassName.get(packageName, v.name);
try { javaFile.writeTo(Paths.get(Main.PARENT_DIR + "generate/")); } catch (IOException e) { e.printStackTrace(); } return v; }
|
七八十个老接口外加对应的 model 瞬间生成简直不能更爽,当然还有 model 复用和一些细节部分没有写的很完善,实际修改代码时改过来就好,工作量不大。