讀完本篇能夠了解的內(nèi)容
1精耐、注解的一些基本使用饭玲;
2、gradle 5.4.1版本中如何正確的導(dǎo)入com.google.auto.service:auto-service:1.0-rc7
類庫;
3扣唱、利用javapoet編寫java文件;
4、如何在編譯期生成代碼噪沙;
5炼彪、利用反射執(zhí)行編譯時生成的java類文件。
起因
項目中看到中臺編寫的router
路由框架可以利用注解注釋后的值進(jìn)行跳轉(zhuǎn)正歼,于是產(chǎn)生了興趣辐马,探究了里面實(shí)現(xiàn)的基本原理。
具體表現(xiàn)如下代碼:
// 注解注釋某個activity
@Route(path = "/module_mall/my_points_activity")
public class MyPointsActivity
// 跳轉(zhuǎn)
ARouter.getInstance().build("/module_mall/my_points_activity")
.navigation();
主要實(shí)現(xiàn)過程如下圖:
實(shí)現(xiàn)過程
自定義Annotation注解
新建一個java library模塊局义,用來編寫注解相關(guān)的代碼@Target(ElementType.TYPE)
表示該注解只能作用于類喜爷,@Retention(RetentionPolicy.CLASS)
表示該注解作用的生命周期為編譯期間。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Router {
String value();
}
自定義AbstractProcessor的子類
接著萄唇,新建另一個java library模塊檩帐,用來編寫AbstractProcessor
的子類。該模塊的gradle
文件定義如下:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7' // 需要引入auto-service中的注解另萤,這里要這樣寫湃密。要不然不能正確生成
compileOnly 'com.google.auto.service:auto-service:1.0-rc7'
implementation 'com.squareup:javapoet:1.13.0' // 方便生成java類
implementation project(':my_annotation') // 這是自定義的模塊
implementation project(path: ':apt') // 這是自定義的模塊
}
sourceCompatibility = "7"
targetCompatibility = "7"
注意點(diǎn):要用annotationProcessor來引入com.google.auto.service:auto-service:1.0-rc7,這樣才能保證正確生成代碼仲墨,這里對應(yīng)第二個問題勾缭。
getSupportedAnnotationTypes
方法返回一個set集合,用來保存編譯器支持掃描的注解類型目养。
@Override
public Set<String> getSupportedAnnotationTypes() {
LinkedHashSet<String> types = new LinkedHashSet<>();
types.add(Router.class.getCanonicalName());
return types;
}
process
方法俩由,在編譯期間會被調(diào)用,此時我們可以利用javapoet
來生成相應(yīng)的java
代碼癌蚁。具體的解釋在代碼中寫的已經(jīng)很清楚幻梯,可以對照著看。
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 得到所有被router注解的類
Set<? extends Element> annotatedElement = roundEnv.getElementsAnnotatedWith(Router.class);
/**
* public void handle(Map<String, Class<?>> routeMap) {
* }
*
* 構(gòu)建map類型的參數(shù)努释,Map<String, Class<?>>類型
*/
ParameterizedTypeName typeName = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(
ClassName.get(Class.class),
WildcardTypeName.subtypeOf(Object.class)
)
);
// 方法名 handle
MethodSpec.Builder methodSpec = MethodSpec.methodBuilder("handle")
.addAnnotation(Override.class) // 給方法添加override注解
.addModifiers(Modifier.PUBLIC) // public類型的方法
.returns(void.class) // 返回值為void
.addParameter(ParameterSpec.builder(typeName, "routeMap").build()); // 方法參數(shù)名 routeMap
for (Element element : annotatedElement) {
// 1碘梢、ElementKind.CLASS表示注解注解的類型是class
// 2、ElementKind.FIELD表示注解的類型是類的屬性
if (element.getKind() == ElementKind.CLASS) {
TypeElement typeElement = (TypeElement) element;
// 獲取注解類的包名信息
PackageElement packageElement = (PackageElement) element.getEnclosingElement();
// 通過類的全名獲取注解類的classname對象
ClassName className = ClassName.get(packageElement.getQualifiedName().toString(), typeElement.getSimpleName().toString());
// 獲取該注解
Router annotation = element.getAnnotation(Router.class);
// 1伐蒂、annotation.value()注解的值
// 2煞躬、$N.put($S,$T.class) 這里有三個占位符$N、$S逸邦、$T
// 3恩沛、$N 可代表方法,變量等等缕减;$S表示字符串雷客;$T表示類,并且會自動import該類
methodSpec.addStatement("$N.put($S,$T.class)", "routeMap", annotation.value(), className);
}
}
ClassName interfaceName = ClassName.get(RouteTable.class);
// 動態(tài)生成類桥狡,類名目前是寫死的搅裙,其實(shí)可以利用包名生成特定的Java類
TypeSpec helloWorld = TypeSpec.classBuilder("PerryRouteTable")
.addModifiers(Modifier.PUBLIC) // public類型的類
.addSuperinterface(interfaceName) // 實(shí)現(xiàn)的接口
.addMethod(methodSpec.build()) // 實(shí)現(xiàn)的方法
.build();
// 動態(tài)生成文件
JavaFile javaFile = JavaFile.builder(AptManager.PACKAGE_NAME, helloWorld)
.build();
try {
//來自javapoet 動態(tài)生成方法
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
此時皱卓,build
一下我們的代碼,就會在如下路徑中
自動生成如下代碼內(nèi)容:
public class PerryRouteTable implements RouteTable {
@Override
public void handle(Map<String, Class<?>> routeMap) {
routeMap.put("third_activity",ThirdActivity.class);
}
}
利用反射調(diào)用build中的代碼
把字符串與類之間的關(guān)系寫入到map
集合中去部逮。
public class AptManager {
public static final String PACKAGE_NAME = "com.perry.router";
private static Map<String, Class<?>> sRouteTable = new HashMap<>();
public static void registerModule(String moduleName) {
String routeTableName = PACKAGE_NAME + "." + moduleName;
try {
Class<?> clazz = Class.forName(routeTableName);
Constructor constructor = clazz.getConstructor();
RouteTable instance = (RouteTable) constructor.newInstance();
instance.handle(sRouteTable);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Class<?> getClazzFromKey(String routerKey) {
if (sRouteTable.isEmpty()) return null;
return sRouteTable.get(routerKey);
}
}
在我們的主模塊中利用反射調(diào)用handle
方法娜汁,我這邊為了簡單起見,就默認(rèn)只生成了一個java
文件甥啄,按理說存炮,不同的模塊就會生成一份不同的java
文件,然后循環(huán)遍歷并且利用反射調(diào)用handle
方法就可以了蜈漓。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AptManager.registerModule("PerryRouteTable");
}
}
以上就是整個實(shí)現(xiàn)過程穆桂,如有問題,歡迎討論融虽。