目录
InterceptorProcessor
上一篇,详细分析了RouteProcessor的内容,该注解处理器的主要作用就是收集@Route的信息。代码比较长,如果你耐心研究完了,那么恭喜,剩下的两个Processor的解读将会一马平川。
首先看init方法,这里就真心不复制粘贴代码了,因为代码几乎和RouteProcessor的init方法一模一样,区别只是如下代码
iInterceptor = elementUtil.getTypeElement(Consts.IINTERCEPTOR).asType();
获取了IInterceptor接口的节点。
process方法同样会进一步调用processInterceptor方法进行真正的处理。
private void parseInterceptors(Set<? extends Element> elements) throws IOException {
......
// Verify and cache, sort incidentally.
for (Element element : elements) {
if (verify(element)) { // Check the interceptor meta
logger.info("A interceptor verify over, its " + element.asType());
Interceptor interceptor = element.getAnnotation(Interceptor.class);
Element lastInterceptor = interceptors.get(interceptor.priority());
if (null != lastInterceptor) { // Added, throw exceptions
throw new IllegalArgumentException(
String.format(Locale.getDefault(), "More than one interceptors use same priority [%d], They are [%s] and [%s].",
interceptor.priority(),
lastInterceptor.getSimpleName(),
element.getSimpleName())
);
}
interceptors.put(interceptor.priority(), element);
} else {
logger.error("A interceptor verify failed, its " + element.asType());
}
}
......
}
private boolean verify(Element element) {
Interceptor interceptor = element.getAnnotation(Interceptor.class);
// It must be implement the interface IInterceptor and marked with annotation Interceptor.
return null != interceptor && ((TypeElement) element).getInterfaces().contains(iInterceptor);
}
遍历所有节点,如果该节点没有有Interceptor注解,或者节点没有直接或者间接继承了IInterceptor接口,那么直接跳过。否则提取@Interceptor对象,如果设置的优先级已经存在,那么就会报错。所以可见,Interceptor的优先级不能重复值。如果不存在,那么存入map中
private void parseInterceptors(Set<? extends Element> elements) throws IOException {
if (CollectionUtils.isNotEmpty(elements)) {
......
// Interface of ARouter.
TypeElement type_ITollgate = elementUtil.getTypeElement(IINTERCEPTOR);
TypeElement type_ITollgateGroup = elementUtil.getTypeElement(IINTERCEPTOR_GROUP);
/**
* Build input type, format as :
*
* ```Map<Integer, Class<? extends ITollgate>>```
*/
ParameterizedTypeName inputMapTypeOfTollgate = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(Integer.class),
ParameterizedTypeName.get(
ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(type_ITollgate))
)
);
// Build input param name.
ParameterSpec tollgateParamSpec = ParameterSpec.builder(inputMapTypeOfTollgate, "interceptors").build();
// Build method : 'loadInto'
MethodSpec.Builder loadIntoMethodOfTollgateBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(tollgateParamSpec);
// Generate
if (null != interceptors && interceptors.size() > 0) {
// Build method body
for (Map.Entry<Integer, Element> entry : interceptors.entrySet()) {
loadIntoMethodOfTollgateBuilder.addStatement("interceptors.put(" + entry.getKey() + ", $T.class)", ClassName.get((TypeElement) entry.getValue()));
}
}
// Write to disk(Write file even interceptors is empty.)
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(NAME_OF_INTERCEPTOR + SEPARATOR + moduleName)
.addModifiers(PUBLIC)
.addJavadoc(WARNING_TIPS)
.addMethod(loadIntoMethodOfTollgateBuilder.build())
.addSuperinterface(ClassName.get(type_ITollgateGroup))
.build()
).build().writeTo(mFiler);
logger.info(">>> Interceptor group write over. <<<");
}
}
即使是在实际使用中,也很难看到比这个更简单清晰的processor。
生成一个 @Override public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {} 方法。
如果上一步保存的map不为空,那么遍历之,每个元素都在其中添加类似如下代码
interceptors.put(interceptor的优先级, 被注解的元素类);
在包com.alibaba.android.arouter.routes中生成一个java文件,类名为ARoute$$Interceptors$$XXX(xxx为传入的moduleName),并且实现了接口IInterrecptorGroup。
在ARouter中
app 模块生成了一个Interceptor类。
test-module-1模块也生成了一个Interceptor类。
AutowiredProcessor
init方法简单到一个字都不想多提。直接看process方法
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (CollectionUtils.isNotEmpty(set)) {
try {
logger.info(">>> Found autowired field, start... <<<");
categories(roundEnvironment.getElementsAnnotatedWith(Autowired.class));
generateHelper();
} catch (Exception e) {
logger.error(e);
}
return true;
}
return false;
}
首先调用了categories方法
private void categories(Set<? extends Element> elements) throws IllegalAccessException {
if (CollectionUtils.isNotEmpty(elements)) {
for (Element element : elements) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
if (element.getModifiers().contains(Modifier.PRIVATE)) {
throw new IllegalAccessException("The autowired fields CAN NOT BE 'private'!!! please check field ["
+ element.getSimpleName() + "] in class [" + enclosingElement.getQualifiedName() + "]");
}
if (parentAndChild.containsKey(enclosingElement)) { // Has categries
parentAndChild.get(enclosingElement).add(element);
} else {
List<Element> childs = new ArrayList<>();
childs.add(element);
parentAndChild.put(enclosingElement, childs);
}
}
logger.info("categories finished.");
}
}
遍历所有被注解的节点,通过getEnclosingElement方法获取该节点所在的类的节点。(请注意,这样的说法是错误的。查看getEnclosingElement的方法可以发现,当element是TypeParameterElement时,才是返回所在类的节点。因为Autowired是用来注解类内字段的,所以相当于放回类节点。)
如果被注解的节点是private的将会直接报错。然后将所有节点根据所在类为key放入一个Map中:
HashMap<节点所在类,Set<节点>> parentAndChild
综上,其实categories的作用就是根据类将被注解的字段进行分类。很符合方法名嘛!
再来看generateHelper方法:
private void generateHelper() throws IOException, IllegalAccessException {
TypeElement type_ISyringe = elementUtil.getTypeElement(ISYRINGE);
TypeMirror iProvider = elementUtil.getTypeElement(Consts.IPROVIDER).asType();
TypeMirror activityTm = elementUtil.getTypeElement(Consts.ACTIVITY).asType();
TypeMirror fragmentTm = elementUtil.getTypeElement(Consts.FRAGMENT).asType();
TypeMirror fragmentTmV4 = elementUtil.getTypeElement(Consts.FRAGMENT_V4).asType();
// Build input param name.
ParameterSpec objectParamSpec = ParameterSpec.builder(TypeName.OBJECT, "target").build();
if (MapUtils.isNotEmpty(parentAndChild)) {
for (Map.Entry<TypeElement, List<Element>> entry : parentAndChild.entrySet()) {
// Build method : 'inject'
MethodSpec.Builder injectMethodBuilder = MethodSpec.methodBuilder(METHOD_INJECT)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(objectParamSpec);
TypeElement parent = entry.getKey();
List<Element> childs = entry.getValue();
String qualifiedName = parent.getQualifiedName().toString();
String packageName = qualifiedName.substring(0, qualifiedName.lastIndexOf("."));
String fileName = parent.getSimpleName() + NAME_OF_AUTOWIRED;
logger.info(">>> Start process " + childs.size() + " field in " + parent.getSimpleName() + " ... <<<");
injectMethodBuilder.addStatement("$T substitute = ($T)target", ClassName.get(parent), ClassName.get(parent));
......
}
遍历上一步存储的parentAndChild中的所有的entry。之后的操作相当于为每一个存在Autowired注解字段的类(即 Key)都创建一个java类,用来进行自动的注入!
在该java类中,首先创建了一个inject方法, @Override public void inject(Object target)。然后在其中插入第一句代码:
XXX substitute = (XXX)target; 将target强转成Key所代表的类的实例。
继续往下走,接下去的代码就是向方法中添加注入代码了。
private void generateHelper() throws IOException, IllegalAccessException {
...............
// Generate method body, start inject.
for (Element element : childs) {
Autowired fieldConfig = element.getAnnotation(Autowired.class);
String fieldName = element.getSimpleName().toString();
if (typeUtil.isSubtype(element.asType(), iProvider)) { // It's provider
if ("".equals(fieldConfig.name())) { // User has not set service path, then use byType.
// Getter
injectMethodBuilder.addStatement(
"substitute." + fieldName + " = $T.getInstance().navigation($T.class)",
ARouterClass,
ClassName.get(element.asType())
);
} else { // use byName
// Getter
injectMethodBuilder.addStatement(
"substitute." + fieldName + " = ($T)$T.getInstance().build($S).navigation();",
ClassName.get(element.asType()),
ARouterClass,
fieldConfig.name()
);
}
// Validater
if (fieldConfig.required()) {
injectMethodBuilder.beginControlFlow("if (substitute." + fieldName + " == null)");
injectMethodBuilder.addStatement(
"throw new RuntimeException(\"The field '" + fieldName + "' is null, in class '\" + $T.class.getName() + \"!\")", ClassName.get(parent));
injectMethodBuilder.endControlFlow();
}
}
.........
}
遍历Key所对应的Set(记录所有该类中包含的autowired字段),如果该字段的类型是IProvider或者其子类,那么他的注入方式和其他注入方式有些区别。
如果@Autowired没有设置name属性,那么使用byType(根据类型生成)的注入方式,在代码中添加如下代码:
substitute.XXX/*XXX表示当前element的字段名*/ = ARouter.getInstance().navigation(XXX/*element的字段类型*/.class);
如果设置了name属性,那么久通过byName的方式注入代码:
substitute.XXX/*XXX表示当前element的字段名*/ = (XXX/*element的字段类型*/)ARouter.getInstance().build(name属性).navigation();
最后如果该Autowired字段标记了required,并且还是为空(即注入内容为空),那么就会添加报错语句,提示注入错误。
关于IProvider的意义和这样做的原因,相信会随着源码的深入逐渐了解。
之后是其他类型的注入
else { // It's normal intent value
String statment = "substitute." + fieldName + " = substitute.";
boolean isActivity = false;
if (typeUtil.isSubtype(parent.asType(), activityTm)) { // Activity, then use getIntent()
isActivity = true;
statment += "getIntent().";
} else if (typeUtil.isSubtype(parent.asType(), fragmentTm) || typeUtil.isSubtype(parent.asType(), fragmentTmV4)) { // Fragment, then use getArguments()
statment += "getArguments().";
} else {
throw new IllegalAccessException("The field [" + fieldName + "] need autowired from intent, its parent must be activity or fragment!");
}
statment = buildStatement(statment, TypeUtils.typeExchange(element.asType()), isActivity);
injectMethodBuilder.addStatement(statment, StringUtils.isEmpty(fieldConfig.name()) ? fieldName : fieldConfig.name());
// Validater
if (fieldConfig.required() && !element.asType().getKind().isPrimitive()) { // Primitive wont be check.
injectMethodBuilder.beginControlFlow("if (substitute." + fieldName + " == null)");
injectMethodBuilder.addStatement(
"throw new RuntimeException(\"The field '" + fieldName + "' is null, in class '\" + $T.class.getName() + \"!\")", ClassName.get(parent));
injectMethodBuilder.endControlFlow();
}
}
接下去是语句的拼装,以上代码最重要的是透露了一个消息,使用@Autowired注解字段的类必须是activity或者fragment的子类。因为注入的实现实际上就是通过getIntent().getXXX 或者 getArguments().getXXX获取内容,自动填入字段。
另外需要注意的一点是buildStatement方法:
private String buildStatement(String statment, int type, boolean isActivity) {
if (type == TypeKind.BOOLEAN.ordinal()) {
statment += (isActivity ? ("getBooleanExtra($S, false)") : ("getBoolean($S)"));
} else if (type == TypeKind.BYTE.ordinal()) {
statment += (isActivity ? ("getByteExtra($S, (byte) 0)") : ("getByte($S)"));
} else if (type == TypeKind.SHORT.ordinal()) {
statment += (isActivity ? ("getShortExtra($S, (short) 0)") : ("getShort($S)"));
} else if (type == TypeKind.INT.ordinal()) {
statment += (isActivity ? ("getIntExtra($S, 0)") : ("getInt($S)"));
} else if (type == TypeKind.LONG.ordinal()) {
statment += (isActivity ? ("getLongExtra($S, 0)") : ("getLong($S)"));
} else if (type == TypeKind.FLOAT.ordinal()) {
statment += (isActivity ? ("getFloatExtra($S, 0)") : ("getFloat($S)"));
} else if (type == TypeKind.DOUBLE.ordinal()) {
statment += (isActivity ? ("getDoubleExtra($S, 0)") : ("getDouble($S)"));
} else if (type == TypeKind.OTHER.ordinal()) {
statment += (isActivity ? ("getStringExtra($S)") : ("getString($S)"));
}
return statment;
}
所列出的内容就表示getXXX所支持的方法,发现没有序列化和数组的支持。是不是意味着不能注入对象以及List等。(高手指正下我分析的对不对……)
然后还是@Autowired.required的验证性判断。
最终,生成java文件:
// Generate autowired helper
JavaFile.builder(packageName,
TypeSpec.classBuilder(fileName)
.addJavadoc(WARNING_TIPS)
.addSuperinterface(ClassName.get(type_ISyringe))
.addModifiers(PUBLIC)
.addMethod(injectMethodBuilder.build())
.build()
).build().writeTo(mFiler);
和其他两个processor有些不容,它生成的java文件的包名为当前key表示的类所在的包名,而不是放在arouter目录下,类名为XXX(key表示的类的类名)$$ARouter$$Autowired. 实现ISyringe接口。
查找了下,在demo中只有app目录下有如下文件生成
打开代码看一下,还是符合上述分析的。
总结
至此,编译阶段的准备工作算是结束了,剩下的代码运行都是在运行阶段,下一篇将会跟着源码的init方法一步分析下去。