写在最后
可能有人会问我为什么愿意去花时间帮助大家实现求职梦想,因为我一直坚信时间是可以复制的。我牺牲了自己的大概十个小时写了这片文章,换来的是成千上万的求职者节约几天甚至几周时间浪费在无用的资源上。
上面的这些(算法与数据结构)+(Java多线程学习手册)+(计算机网络顶级教程)等学习资源
- 判断是否所有的注解处理器都已执行完毕,如果没有,继续下一个注解处理器的执行(回到步骤1)
Butterknife注解处理器的例子
Butterknife的注解处理器的工作方式如下:
-
定义一个非私有的属性变量
-
添加该属性变量的注解和传入id
-
调用
Butterknife.bind(..)
方法。
当你点击Android Studio的Build
按钮时,Butterknife先是按照上述步骤生成了对应的辅助类和方法。在代码执行到bind(..)
方法时,Butterknife就去调用之前生成的辅助类方法,完成对被注解元素的赋值操作。
自定义注解处理器
了解了基本的知识点后,我们应该尝试去使用这些技巧。 接下来是实践时间,我们来开发一个简单的例子,利用注解处理器来自动产生随机数字和随机字符串。
-
首先创建一个project。
-
创建lib_annotations, 这是一个纯java的module,不包含任何android代码,只用于存放注解。
-
创建lib_compiler, 这同样是一个纯java的module。该module依赖于步骤2创建的module_annotation,处理注解的代码都在这里,该moduule最终不会被打包进apk,所以你可以在这里导入任何你想要的任意大小依赖库。
-
创建lib_api, 对该module不做要求,可以是android library或者java library或者其他的。该module用于调用步骤3生成的辅助类方法。
image
1. 添加注解
在lib_annotations中添加两个注解:RandomString
, RandomInt
,分别用于生成随机数字和随机字符串:
@Retention(CLASS)
@Target(value = FIELD)
public @interface RandomString {
}
@Retention(CLASS)
@Target(value = FIELD)
public @interface RandomInt {
int minValue() default 0;
int maxValue() default 65535;
}
-
@interface 自定义注解,使用 @interface 作为类名修饰符
-
@Target 该注解所能修饰的元素类型,可选类型如下:
public enum ElementType {
TYPE, //类
FIELD, //属性
METHOD, //方法
PARAMETER, //参数
CONSTRUCTOR, //构造函数
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE;
private ElementType() {
}
}
- @Retention 该注解的保留策略,有三种选项:
public enum RetentionPolicy {
SOURCE, //被编译器所忽略
CLASS, //被编译器保留至类文件,但不会保留至运行时
RUNTIME //保留至类文件,且保留至运行时,能在运行时反射该注解修饰的对象
}
2. 注解处理器
真正处理注解并生成代码的操作都在这里。 在写代码之前我们需要先导入两个重要的库,以及我们的注解模块:
compile ‘com.google.auto.service:auto-service:1.0-rc4’
compile ‘com.squareup:javapoet:1.9.0’
implementation project(‘:lib_annotations’)
新建类RandomProcessor.java
:
@AutoService(Processor.class)
public class RandomProcessor extends AbstractProcessor{
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
@Override
public Set getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
}
- @AutoService @AutoService(Processor.class)会告诉编译器该注解处理器的存在,并在编译时自动在
META-INF/services
下生成javax.annotation.processing.Processor
文件,文件的内容为
com.rhythm7.lib_compiler.RandomProcessor
也就是说,你所声明的注解处理器都会在被写入这个配置文件中。 这样子,当外部程序装载这个模块的时候,就能通过该模块的jar包下的META-INF/services下找到具体的注解处理器的实现类名,并加载实例化,完成模块的注入。 注解处理器需要实现AbstractProcessor
接口,并实现对应的方法
- init() 可选 在该方法中可以获取到
processingEnvironment
对象,借由该对象可以获取到生成代码的文件对象, debug输出对象,以及一些相关工具类
- getSupportedSourceVersion() 返回所支持的java版本,一般返回当前所支持的最新java版本即可
- getSupportedAnnotationTypes() 你所需要处理的所有注解,该方法的返回值会被
process()
方法所接收
- process() 必须实现 扫描所有被注解的元素,并作处理,最后生成文件。该方法的返回值为boolean类型,若返回true,则代表本次处理的注解已经都被处理,不希望下一个注解处理器继续处理,否则下一个注解处理器会继续处理。
初始化
较详细代码如下:
private static final List<Class<? extends Annotation>> RANDOM_TYPES
= Arrays.asList(RandomInt.class, RandomString.class);
private Messager messager;
private Types typesUtil;
private Elements elementsUtil;
private Filer filer;
private TypeonProcess()per.init(processingEnv);
messager = processingEnv.getMessager();
typesUtil = processingEnv.getTypeUtils();
elementsUtil = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set getSupportedAnnotationTypes() {
Set annotations = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : RANDOM_TYPES) {
annotations.add(annotation.getCanonicalName());
}
return annotations;
}
处理注解
在process()
方法中执行以下操作:
1.扫描所有注解元素,并对注解元素的类型做判断
for (Element element : roundEnv.getElementsAnnotatedWith(RandomInt.class)) {
//AnnotatedRandomInt是对被RandomInt注解的Elment的简单封装
AnnotatedRandomInt randomElement = new AnnotatedRandomInt(element);
messager.printMessage(Diagnostic.Kind.NOTE, randomElement.toString());
//判断被注解的类型是否符合要求
if (!element.asType().getKind().equals(TypeKind.INT)) {
messager.printMessage(Diagnostic.Kind.ERROR, randomElement.getSimpleClassName().toString() + “#”
- randomElement.getElementName().toString() + " is not in valid type int");
}
//按被注解元素所在类的完整类名为key将被注解元素存储进Map中,后面会根据key生成类文件
String qualifier = randomElement.getQualifiedClassName().toString();
if (annotatedElementMap.get(qualifier) == null) {
annotatedElementMap.put(qualifier, new ArrayList());
}
annotatedElementMap.get(qualifier).add(randomElement);
}
生成类文件
将之前以注解所在类为key的map遍历,并以key值为分组生成类文件。
for (Map.Entry<String, List> entry : annotatedElementMap.entrySet()) {
MethodSpec constructor = createConstructor(entry.getValue());
TypeSpec binder = createClass(getClassName(entry.getKey()), constructor);
JavaFile javaFile = JavaFile.builder(getPackage(entry.getKey()), binder).build();
javaFile.writeTo(filer);
}
生成类、构造函数、代码段以及文件都是利用到了javapoet
依赖库。当然你也可以选择拼接字符串和自己用文件IO写入,但是用javapoet
要更方便得多。
private MethodSpec createConstructor(List randomElements) {
AnnotatedRandomElement firstElement = randomElements.get(0);
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(TypeName.get(firstElement.getElement().getEnclosingElement().asType()), “target”);
for (int i = 0; i < randomElements.size(); i++) {
addStatement(builder, randomElements.get(i));
}
return builder.build();
}
private void addStatement(MethodSpec.Builder builder, AnnotatedRandomElement randomElement) {
builder.addStatement(String.format(
“target.%1 s = s = %2 s=s”,
randomElement.getElementName().toString(),
randomElement.getRandomValue())
);
}
private TypeSpec createClass(String className, MethodSpec constructor) {
return TypeSpec.classBuilder(className + “_Random”)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(constructor)
.build();
}
private String getPackage(String qualifier) {
return qualifier.substring(0, qualifier.lastIndexOf(“.”));
}
private String getClassName(String qualifier) {
return qualifier.substring(qualifier.lastIndexOf(“.”) + 1);
}
最后
这份清华大牛整理的进大厂必备的redis视频、面试题和技术文档
祝大家早日进入大厂,拿到满意的薪资和职级~~~加油!!
感谢大家的支持!!
}
private String getPackage(String qualifier) {
return qualifier.substring(0, qualifier.lastIndexOf(“.”));
}
private String getClassName(String qualifier) {
return qualifier.substring(qualifier.lastIndexOf(“.”) + 1);
}
最后
这份清华大牛整理的进大厂必备的redis视频、面试题和技术文档
祝大家早日进入大厂,拿到满意的薪资和职级~~~加油!!
感谢大家的支持!!
[外链图片转存中…(img-1p6xAZJ6-1715815393419)]