什么是AOP?
AOP面向切面编程,就是在代码预编译阶段,在不修改源代码的情况下,给程序添加某一功能。
像成熟的框架,ARouter,ButterKnife等也都使用了这个技术。任何技术的出现都有其实际应用场景,为了解决某一方面的痛点。AOP的出现让某些功能组件的封装更加解耦,使用者能够更加的方便的使用组件里的功能。
拿ButterKnife举例,我们原生开发,以前经常写很多findViewById的代码,显然这类代码写起来很繁琐,且容易出错(id和view有时候没对上)。而AOP可以有效避免这些问题。
比如我们可以通过在预编译的阶段解析注解,然后生成对应的java文件,该java文件封装了findviewbyid的方法,实现view和id的动态绑定。这样就非常有效减少了后期的编写代码的工作量,可以快速实现view和id的绑定操作。
AOP编程思路:
一.定义注解:自定义注解参考下面例子即可,default表示该字段不是必传字段,下面例子的自定义注解在使用的时候必须传入name。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Laucher {
String name();
boolean async() default false;
int priority() default -1;
String[] preTasks() default {};
}
元注解 说明
@Target 定义该自定义注解类型。
@Retention 这个注解的的存活时间
@Document 表明注解可以被javadoc此类的工具文档化
@Inherited 是否允许子类继承该注解,默认为false
@Target 类型 说明
ElementType.TYPE 接口、类、枚举、注解
ElementType.FIELD 字段、枚举的常量
ElementType.METHOD 方法
ElementType.PARAMETER 方法参数
ElementType.CONSTRUCTOR 构造函数
ElementType.LOCAL_VARIABLE 局部变量
ElementType.ANNOTATION_TYPE 注解
ElementType.PACKAGE 包
@Retention 说明
RetentionPolicy.SOURCE 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
RetentionPolicy.CLASS 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
RetentionPolicy.RUNTIME 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
二.编译阶段解析注解:创建一个java-library(不是androd library),实现自定义Processor,注册Processor。引入auto-service可以实现自动注册。rebuild之后,在build目录会生成javax.annotation.processing.Processor文件,这个文件能看到你注册的Processor。
dependencies {
implementation project(path: ':xlauncher-annotation')
implementation 'com.squareup:javapoet:1.8.0'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
compileOnly 'com.google.auto.service:auto-service-annotations:1.0-rc7'
}
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"com.azx.xlauncher_annotation.Laucher"})
@AutoService(Processor.class)
public class LauncherProcessor extends AbstractProcessor {
private Map<String, LauncherTask> taskMap = new HashMap<>();
private String inputParamsName = "taskMap";
private Filer filer;
private Writer docWriter;
private Logger logger;
Elements elementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
logger = new Logger(processingEnv.getMessager());
filer = processingEnv.getFiler();
elementUtils = processingEnv.getElementUtils();
try {
docWriter = filer.createResource(StandardLocation.SOURCE_OUTPUT,
PACKAGE_OF_GENERATE_DOCS,
PACKAGE_OF_GENERATE_DOCS+".json").openWriter();
} catch (IOException e) {
logger.error(e);
}
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> launcherElements = roundEnvironment.getElementsAnnotatedWith(Laucher.class);
parseElements(launcherElements);
try {
createJavaDoc(launcherElements);
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
private void createJavaDoc(Set<? extends Element> elements) throws IOException {
ParameterizedTypeName mapTaskLauncher = ParameterizedTypeName.get(
ClassName.get(HashMap.class),
ClassName.get(String.class),
ClassName.get(LauncherTask.class)
);
ParameterSpec paramSpec = ParameterSpec.builder(mapTaskLauncher, inputParamsName).build();
MethodSpec.Builder method = MethodSpec.methodBuilder(METHOD_NAME)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(paramSpec);
addCodeIntoMethod(method, elements);
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(CLASS_NAME) //class文件名字
.addModifiers(Modifier.PUBLIC) //公有方法
.addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITASK_MANAGER)))
.addJavadoc("@Launcher注解自动生成的java文件")
.addMethod(method.build())
.build()
).build().writeTo(filer);
}
private void addCodeIntoMethod(MethodSpec.Builder method, Set<? extends Element> elements) {
ClassName classTask = ClassName.get(LauncherTask.class);
Set<String> keys = taskMap.keySet();
for (Element item : elements) {
Laucher annotation = item.getAnnotation(Laucher.class);
String key = annotation.name();
logger.info(key + " " + ClassName.get((TypeElement)item));
method.addStatement("$T.build($S, $T.class, " + taskMap.get(key).getPriority() + "," + taskMap.get(key).isAsync() + ", " + createListMethod(taskMap.get(key).getPreTaskList()) + ")",
classTask,
taskMap.get(key).getTaskName(),
ClassName.get((TypeElement)item));
}
}
private String createListMethod(List<String> list) {
List<String> test = new ArrayList() {{
add("a");
}};
StringBuilder builder = new StringBuilder();
for (String item : list) {
builder.append("add(\"" + item + "\"); ");
}
return "new java.util.ArrayList<String>(){{" + builder.toString() + "}}";
}
private void parseElements(Set<? extends Element> elements) {
taskMap.clear();
for (Element item : elements) {
LauncherTask launcherTask = new LauncherTask();
Laucher annotation = item.getAnnotation(Laucher.class);
launcherTask.setTaskName(annotation.name());
launcherTask.setAsync(annotation.async());
launcherTask.setPriority(annotation.priority());
launcherTask.setPreTaskList(Arrays.asList(annotation.preTasks()));
taskMap.put(launcherTask.getTaskName(), launcherTask);
}
}
}
三.生成java文件:
我们可以看上面的LauncherProcessor类的createJavaCode的方法,里面的代码是整个生成java文件的逻辑。
我们先通过parseElements方法获取使用到Laucher注解的Elements,然后解析Laucher注解内部的参数。
接下来我们一步步阅读代码
ParameterizedTypeName mapTaskLauncher = ParameterizedTypeName.get( ClassName.get(HashMap.class), ClassName.get(String.class), ClassName.get(LauncherTask.class) );
//这个代码实际上就是创建一个参数类型,对应的代码是HashMap<String, LauncherTask>private String inputParamsName = "taskMap"; ParameterSpec paramSpec = ParameterSpec.builder(mapTaskLauncher, inputParamsName).build();
//这个代码就是生成一个参数,对应的java代码是 HashMap<String, LauncherTask> taskMap//下面的代码是构建一个方法对象,public属性,入参是HashMap<String, LauncherTask> taskMap,带有Override注解
MethodSpec.Builder method = MethodSpec.methodBuilder(METHOD_NAME) .addAnnotation(Override.class) .addModifiers(PUBLIC) .addParameter(paramSpec); addCodeIntoMethod(method, elements);
private void addCodeIntoMethod(MethodSpec.Builder method, Set<? extends Element> elements) { ClassName classTask = ClassName.get(LauncherTask.class); Set<String> keys = taskMap.keySet(); for (Element item : elements) { Laucher annotation = item.getAnnotation(Laucher.class); String key = annotation.name(); logger.info(key + " " + ClassName.get((TypeElement)item)); method.addStatement("$T.build($S, $T.class, " + taskMap.get(key).getPriority() + "," + taskMap.get(key).isAsync() + ", " + createListMethod(taskMap.get(key).getPreTaskList()) + ")", classTask, taskMap.get(key).getTaskName(), ClassName.get((TypeElement)item)); } }
//在方法体里面插入代码。
JavaFile.builder(PACKAGE_OF_GENERATE_FILE, TypeSpec.classBuilder(CLASS_NAME) //class文件名字 .addModifiers(Modifier.PUBLIC) //公有方法 .addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITASK_MANAGER))) .addJavadoc("@Launcher注解自动生成的java文件") .addMethod(method.build()) .build() ).build().writeTo(filer);
构建class,并把方法插入,通过javadoc接口加入类的注解,并设置class实现的接口等,然后写入文件。
经过上面代码的处理,我们可以在build目录下面找到生成后的java文件,下图就是生成代码的结果。
四.可能遇到的问题:
1.Processor,init方法没走!
可能原因1:配置问题,jdk没统一,如果设置的jdk-8,结果annotation和compile的lbrary设置了java-7,其次也有可能是androd studio的jdk设置不是jdk-8。
可能原因2:SupportedAnnotationTypes设置错误,在log里面警告没找到对应注解。
2.Processor,init方法走了,但是process方法不执行。
1.java定义的注解用在了kotlin文件上面
2.某个模块使用了该注解,也implement了annotation的lib,但是没导入有自定义Processor的compile的lib。