一:概述
上一篇我们用普通的方式完成了披萨店的代码,下面我们用注解的方式去实现,彻底解决这个需求的痛点。
自定义注解系列文章
- 那些高端、优雅的注解是怎么实现的<0> -- 注解的分类
- 那些高端、优雅的注解是怎么实现的 <1> -- 自定义注解语法
- 那些高端、优雅的注解是怎么实现的<2> -- 解析注解
- 那些高端、优雅的注解是怎么实现的<3> - 可继承性@Inherited
- 那些高端、优雅的注解是怎么实现的<4> -- 使用Annotaion Processing Tool 解析注解
- 那些高端、优雅的注解是怎么实现的<5> --使用Annotaion Processing Tool 自定义注解
- 那些高端、优雅的注解是怎么实现的<6> --自定义持久层框架-类 Hibernate
分析
其实现在最大的痛点是if else
语句。如果可以自动生成就好了。通过上面的描述,我们知道自定义注解可以自动生成我们需要的代码。要实现这个需求我们还需要三个开源库的帮忙
- android-apt
Android Studio原本是不支持注解处理器的, 但是用android-apt
这个插件后, 我们就可以使用注解处理器了, 这个插件可以自动的为生成的代码创建目录, 让生成的代码编译到APK里面去, 而注解处理器本身的代码并不打包到APK
包里。 因为这部分代码只是编译的时候需要用来生成代码, 最终运行的时候是没有用的。
也就是说它主要有两个目的: - 允许配置注解处理器,但注解处理器的代码不会添加到最后的APK或library中.使用
annotationProcessor
添加注解处理器为依赖。 - 设置源路径,使注解处理器生成的代码能被Android Studio正确的引用.
-
Google Auto
Google Auto
的主要作用是注解Processor
类,并对其生成META-INF
的配置信息, 可以让你不用去写META-INF
这些配置文件,只要在自定义的Processor
上面加上@AutoService(Processor.class)
- javapoet
javapoet:A Java API for generating java source files
.它可以帮助我们通过类调用的形式来生成代码。
二 :模块分割
综上所述,我们应该把注解的代码和业务逻辑的代码分开。注解的定义代码和注解的解析代码也分开。这样我们就可以定义为三个模块。module app 、module api、module compiler。
- module app :业务相关逻辑代码
- module api :定义注解。这个模块会被app 模块和 compiler 模块引用
- module compiler :定义注解的解析方法。也就是定义 processor.这个模块不会打包到应用中。在编译过程中会生成我们定义的java 代码。这些代码会被打包到apk 中。
注意:一定要使用jdk1.7,1.8对注解的支持有bug。
app module
需要在 gradle 中依赖 api module 和 compiler module .
由于 compiler module 是生成代码的模块。所以需要用annotationProcessor
依赖。
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.tuoanlan.androidannotationdemo"
minSdkVersion 26
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
//依赖api module
implementation project(':api')
// 依赖本地生成的Annoation project
annotationProcessor project(':compiler')
}
注意:这里提个醒,有些文章说 apt
的依赖方式应该像下面这样,但注意 gradle 3.0 以后的版本,不适用这种方式,会报错的。所以根据你的gradle 版本,来选择适合的 apt
依赖方式。
apply plugin: 'com.android.application'
// apt(gradle 3.0 后的错误的配置)
apply plugin: 'com.neenbedankt.android-apt'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.tuoanlan.androidannotationdemo"
minSdkVersion 26
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
// 配置apt(gradle 3.0 后的错误的配置)
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
//依赖api module
implementation project(':api')
}
报错信息如下
android-apt plugin is incompatible with the Android Gradle plugin. Please use 'annotationProcessor' configuration instead.
在app module里我们定义各种披萨。比如
/**
* 披萨品种-提拉米苏
*/
@Factory(
id = "Tiramisu",
type = Meal.class
)
public class Tiramisu implements Meal {
@Override
public float getPrice() {
return 4.5f;
}
}
注意:@Factory 注解在 api 模块里定义的
api module
api module 我们定义为 java-library
gradle 内容如下 。
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
//使用jdk1。7
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
定义注解@Factory
@Target(ElementType.TYPE)//可用在:类、接口(包括注释类型)或枚举声明
@Retention(RetentionPolicy.CLASS)//生命周期为class ,编译成.class 文件仍然存在
public @interface Factory {
/**
* The name of the factory
*/
Class type();
/**
* The identifier for determining which item should be instantiated
*/
String id();
}
compiler module
由于compiler中会解析注解并生成我们需要的代码。所以需要用到auto-service
和 javapoet
库,所以不要忘记添加依赖。另外compiler 模块中会用到 api 模块的注解,所以也需要添加 api 模块的依赖。整个 gradle 配置如下。
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//依赖 module api
implementation project(':api')
// 主要作用是注解Processor类,并对其生成META-INF的配置信息,
// 可以让你不用去写META-INF这些配置文件,
// 只要在自定义的Processor上面加上@AutoService(Processor.class)
compile group: 'com.google.auto.service', name: 'auto-service', version: '1.0-rc6'
//A Java API for generating .java source files.
// 可以更方便的生成代码,它可以帮助我们通过类调用的形式来生成代码。
compile 'com.squareup:javapoet:1.11.1'
}
//使用jdk1。7
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
三:定义储存注解信息的bean 类
在compiler module
中当我们读取到注解中所携带的信息后,需要把信息储存在类里方便我们调用。所以我们定义FactoryAnnotatedClass
类。用来储存@Factory
注解的相关信息。
package com.example.compiler;
import com.example.api.Factory;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.MirroredTypeException;
public class FactoryAnnotatedClass {
/**
* TypeElement 信息
* */
private TypeElement annotatedClassElement;
/**
*{@link Factory#type()}指定的类型合法全名 ,在我这个项目里指 app 项目里的 Meal
* */
private String qualifiedSuperClassName;
private String simpleTypeName;
/**
* {@link Factory#id()}中指定的id
* */
private String id;
/**
* 在 FactoryAnnotatedClass 中,我们保存被注解类的数据,比如合法的类的名字,以及 @Factory 注解本身的一些信息。
* 也就是说每一个使用了 @Factory 注解的类都对应一个 FactoryAnnotatedClass 类
* */
public FactoryAnnotatedClass(TypeElement classElement) throws IllegalArgumentException {
//由于实质上就是被注解的类(比如本项目中的 Tiramisu)
this.annotatedClassElement = classElement;
//获取到注解
Factory annotation = classElement.getAnnotation(Factory.class);
//取出注解的中得id
id = annotation.id();
//如果id 为空则抛出错误
if ("".equalsIgnoreCase(id)) {
throw new IllegalArgumentException(
String.format("id() in @%s for class %s is null or empty! that's not allowed",
Factory.class.getSimpleName(), classElement.getQualifiedName().toString()));
}
// Get the full QualifiedTypeName
try {
//取出注解的中得 type,它是个 class,我们这个项目指定的type 为 Meal,也就是 Tiramisu 等类实现的接口
Class<?> clazz = annotation.type();
// 返回底层阶级Java语言规范中定义的标准名称。
qualifiedSuperClassName = clazz.getCanonicalName();
//获取简单的类名(Meal的简名)
simpleTypeName = clazz.getSimpleName();
} catch (MirroredTypeException mte) {
DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();
TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
qualifiedSuperClassName = classTypeElement.getQualifiedName().toString();
simpleTypeName = classTypeElement.getSimpleName().toString();
}
}
/**
* 获取在{@link Factory#id()}中指定的id
* return the id
*/
public String getId() {
return id;
}
/**
* 获取在{@link Factory#type()}指定的类型合法全名 ,本项目为Meal 的标准合法全名
*
* @return qualified name
*/
public String getQualifiedFactoryGroupName() {
return qualifiedSuperClassName;
}
/**
* 获取在{@link Factory#type()}{@link Factory#type()}指定的类型的简单名字
*
* @return qualified name
*/
public String getSimpleFactoryGroupName() {
return simpleTypeName;
}
/**
* 获取被 TypeElement(被@Factory 注解的类的 TypeElement)
*/
public TypeElement getTypeElement() {
return annotatedClassElement;
}
}
四: 定义储存工厂类信息的类
本例子仅仅自动生成 MealFactory
的代码,但如果后面我们需要生产其他 Factory
呢?,如drinksFactory 呢?所以我们需要定义一个类用来储存相关信息。
package com.example.compiler;
import com.example.api.Factory;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.processing.Filer;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
public class FactoryGroupedClasses {
/**
* 将被添加到生成的工厂类的名字中
*/
private static final String SUFFIX = "Factory";
/**
* 本例指 Meal 的合法全名
*/
private String qualifiedClassName;
/**
*
* */
private Map<String, FactoryAnnotatedClass> itemsMap =
new LinkedHashMap<String, FactoryAnnotatedClass>();
//用 Meal 的合法全名作为参数,生成 FactoryGroupedClasses
public FactoryGroupedClasses(String qualifiedClassName) {
this.qualifiedClassName = qualifiedClassName;
}
/**
* 添加方法,向itemMap 中插入
* {@link FactoryAnnotatedClass}
*/
public void add(FactoryAnnotatedClass toInsert) throws ProcessingException {
/**
*查询 {@link itemsMap } 中是否有该id 的 FactoryAnnotatedClass
* */
FactoryAnnotatedClass existing = itemsMap.get(toInsert.getId());
if (existing != null) {
// Alredy existing
throw new ProcessingException(toInsert.getTypeElement(),
"Conflict: The class %s is annotated with @%s with id ='%s' but %s already uses the same id",
toInsert.getTypeElement().getQualifiedName().toString(), Factory.class.getSimpleName(),
toInsert.getId(), existing.getTypeElement().getQualifiedName().toString());
}
itemsMap.put(toInsert.getId(), toInsert);
}
/**
*
*
* */
public void generateCode(Elements elementUtils, Filer filer) throws IOException {
//通过工厂接口的合法全类名获取到工厂接口的 TypeElement(本例的工厂为 Meal)
TypeElement superClassName = elementUtils.getTypeElement(qualifiedClassName);
//获取生成的class 名称(本例生成的为 MealFactory)
String factoryClassName = superClassName.getSimpleName() + SUFFIX;
//获取包元素
PackageElement pkg = elementUtils.getPackageOf(superClassName);
//获取包名
String packageName = pkg.isUnnamed() ? null : pkg.getQualifiedName().toString();
//生成一个方法名:create,参数:String 类型 叫id,返回值
MethodSpec.Builder method = MethodSpec.methodBuilder("create")
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "id")
.returns(TypeName.get(superClassName.asType()));
// check if id is null(生成判断id 是否为空的判断逻辑)
method.beginControlFlow("if (id == null)")
.addStatement("throw new IllegalArgumentException($S)", "id is null!")
.endControlFlow();
// Generate items map
//遍历被注解的类的封装类 FactoryAnnotatedClass
for (FactoryAnnotatedClass item : itemsMap.values()) {
method.beginControlFlow("if ($S.equals(id))", item.getId())
.addStatement("return new $L()", item.getTypeElement().getQualifiedName().toString())
.endControlFlow();
}
method.addStatement("throw new IllegalArgumentException($S + id)", "Unknown id = ");
TypeSpec typeSpec = TypeSpec.classBuilder(factoryClassName).addMethod(method.build()).build();
// Write file
JavaFile.builder(packageName, typeSpec).build().writeTo(filer);
}
}
五:实现 AbstractProcessor
实现 AbstractProcessor
,解析注解 @Factory
。
//这个不要忘记了哦
@AutoService(Processor.class)
public class FactoryProcessor extends AbstractProcessor {
//处理TypeMirror的工具类
private Types typeUtils;
//处理Element的工具类
private Elements elementUtils;
// 用来创建你要创建的文件
private Filer filer;
//提供给注解处理器一个报告错误、警告以及提示信息的途径。
private Messager messager;
//类名和 FactoryGroupedClasses 的映射(一个接口映射一个 FactoryGroupedClasses,本例 Meal 的类名映射一个 FactoryGroupedClasses)
private Map<String, FactoryGroupedClasses> factoryClasses = new LinkedHashMap<String, FactoryGroupedClasses>();
/**
* 需要强调的是 Element 代表一个元素,包、类、方法、变量 这些都是 Element
* 在注解的处理过程中会扫描所有的java源文件。源代码中的每一个部分都是一个特定类型的 Element
* 可见 Foo 类,特意新建了一个类来说明
*/
//初始化操作的方法,RoundEnvironment会提供很多有用的工具类Elements、Types和Filer等。
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
typeUtils = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<>();
annotataions.add(Factory.class.getCanonicalName());
return annotataions;
}
//这相当于每个处理器的主函数main()。在该方法中去扫描、评估、处理以及生成Java文件。
// 因此 Element代表的是源代码。TypeElement代表的是源代码中的类型元素,
// 然而TypeElement并不包含类本身的信息
// 你可以从TypeElement中获取类的名字,但是你获取不到类的信息,例如它的父类。
// 这种信息需要通过TypeMirror获取。你可以通过调用elements.asType()获取元素的TypeMirror。
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
// roundEnv.getElementsAnnotatedWith(Factory.class))返回所有被注解了@Factory的元素的列表
//所有元素列表。也就是包括 类、包、方法、变量等
//roundEnv.getElementsAnnotatedWith(Factory.class)
//遍历所有的元素列表
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
//由于返回的是所有元素,所以还要判断该元素是不是一个类
if (annotatedElement.getKind() != ElementKind.CLASS) {
error(annotatedElement, "Only classes can be annotated with @%s",
Factory.class.getSimpleName());
return true; // 退出处理
}
// Element 的元素类型是 ElementKind.CLASS,所以可以直接强制转换(实际上就是被@Factory 注解的类 )
TypeElement typeElement = (TypeElement) annotatedElement;
try {
//生成 FactoryAnnotatedClass,将 typeElement保存的 FactoryAnnotatedClass 中,
// 之后用于判断该元素是否为符合我们要求的元素
FactoryAnnotatedClass factoryAnnotatedClass = new FactoryAnnotatedClass(typeElement);
/**
*
* 检测类是否符合要求:
* 1。是一个公开类
* 2。 只要一个公开的构造函数
* 3。 不是抽象类
* 4。继承于特定的类型,
* */
if (!isValidClass(factoryAnnotatedClass)) {
return true; // 已经打印了错误信息,退出处理过程
}
//从缓存Map 里获取
FactoryGroupedClasses factoryClass =
factoryClasses.get(factoryAnnotatedClass.getQualifiedFactoryGroupName());
if (factoryClass == null) {
//Meal 的合法全名
String qualifiedGroupName = factoryAnnotatedClass.getQualifiedFactoryGroupName();
//用 Meal 的合法全名作为参数,生成 FactoryGroupedClasses
factoryClass = new FactoryGroupedClasses(qualifiedGroupName);
//用 Meal 的合法全名作为key FactoryGroupedClasses的实例作为值存入Map
factoryClasses.put(qualifiedGroupName, factoryClass);
}
// 如果和其他的@Factory标注的类的id相同冲突,
// 抛出IdAlreadyUsedException异常
//将包含被@Factory 注解的类的信息的 FactoryAnnotatedClass
// 的实例加入到 FactoryGroupedClasses 中的 map 中(经过多次循环,本项目会存入三个
//分别为 Tiramisu ,Margherita,Calzone)
factoryClass.add(factoryAnnotatedClass);
} catch (IllegalArgumentException e) {
// @Factory.id()为空 --> 打印错误信息
error(typeElement, e.getMessage());
} catch (ProcessingException e) {
error(e.getElement(), e.getMessage());
}
}
// 为每个工厂生成Java文件
//本例只有一个工厂 Meal 工厂
try {
for(FactoryGroupedClasses factoryClass : factoryClasses.values()) {
factoryClass.generateCode(elementUtils, filer);
}
// Clear to fix the problem
factoryClasses.clear();
} catch (IOException e) {
error(null, e.getMessage());
}
return true;
}
/**
* 使用{@link #messager } 工具打印错误消息
*/
private void error(Element e, String msg, Object... args) {
messager.printMessage(
Diagnostic.Kind.ERROR,
String.format(msg, args),
e);
}
/**
* 检查类{@link FactoryAnnotatedClass 是否符合要求}
* <p>
* 检测类是否符合要求:
* 1。是一个公开类
* 2。 只要一个公开的构造函数
* 3。 不是抽象类
* 4。继承于特定的类型,
*/
private boolean isValidClass(FactoryAnnotatedClass item) {
// 转换为TypeElement, 含有更多特定的方法(就是被@Factory 注解的类的类型)
TypeElement classElement = item.getTypeElement();
//是否是一个公开的类(class 前面的修饰语是否包含 PUBLIC)
if (!classElement.getModifiers().contains(Modifier.PUBLIC)) {
error(classElement, "The class %s is not public.",
classElement.getQualifiedName().toString());
return false;
}
// 检查是否是一个抽象类(被 @Factory 注解的不能是个抽象类)
if (classElement.getModifiers().contains(Modifier.ABSTRACT)) {
error(classElement, "The class %s is abstract. You can't annotate abstract classes with @%",
classElement.getQualifiedName().toString(), Factory.class.getSimpleName());
return false;
}
// 检查继承关系: 必须是@Factory.type()指定的类型子类(获取 Meal TypeElement(类型元素))
TypeElement superClassElement =
elementUtils.getTypeElement(item.getQualifiedFactoryGroupName());
//是否是个接口 interface(这里指 Meal )
if (superClassElement.getKind() == ElementKind.INTERFACE) {
// 检查接口是否实现了
//被 @Factory 注解的类实现的接口是否包含 superClassElement (这里指 Meal)
if (!classElement.getInterfaces().contains(superClassElement.asType())) {
error(classElement, "The class %s annotated with @%s must implement the interface %s",
classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
item.getQualifiedFactoryGroupName());
return false;
}
}
//如果()
else {
// 检查子类 ,classElement 是被@Factory 注解的类
TypeElement currentClass = classElement;
while (true) {
TypeMirror superClassType = currentClass.getSuperclass();
if (superClassType.getKind() == TypeKind.NONE) {
// 到达了基本类型(java.lang.Object), 所以退出
error(classElement, "The class %s annotated with @%s must inherit from %s",
classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
item.getQualifiedFactoryGroupName());
return false;
}
//父类就是要求的父类(这里指 Meal)
if (superClassType.toString().equals(item.getQualifiedFactoryGroupName())) {
// 找到了要求的父类
break;
}
// 在继承树上继续向上搜寻
currentClass = (TypeElement) typeUtils.asElement(superClassType);
}
}
// 检查是否提供了默认公开构造函数(@被 Factory 注解的类是否 有 公开的构造 函数)
for (Element enclosed : classElement.getEnclosedElements()) {
if (enclosed.getKind() == ElementKind.CONSTRUCTOR) {
ExecutableElement constructorElement = (ExecutableElement) enclosed;
//构造函数是公开的,切构造函数参数为0
if (constructorElement.getParameters().size() == 0 && constructorElement.getModifiers()
.contains(Modifier.PUBLIC)) {
// 找到了默认构造函数
return true;
}
}
}
// 没有找到默认构造函数
error(classElement, "The class %s must provide an public empty default constructor",
classElement.getQualifiedName().toString());
return false;
}
}
提醒:在 FactoryProcessor 类的上方,不要忘记添加 @AutoService(Processor.class)
六:解析注解
关于 AbstractProcessor
中的各个函数的功能,我们已经在上一篇文章中做过介绍。这里只关注整个解析过程。
第一步:获取工具类
在init
方法里,ProcessingEnvironment
给我们提供了很多非常有用的工具类。
//初始化操作的方法,RoundEnvironment会提供很多有用的工具类Elements、Types和Filer等。
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//处理TypeMirror的工具类
typeUtils = processingEnv.getTypeUtils();
//处理Element的工具类
elementUtils = processingEnv.getElementUtils();
// 用来创建你要创建的文件
filer = processingEnv.getFiler();
//提供给注解处理器一个报告错误、警告以及提示信息的途径。
messager = processingEnv.getMessager();
}
第二步:遍历被@Factory 注解的元素
在方法 process
中,可以通过 roundEnv.getElementsAnnotatedWith(Factory.class))
返回所有被注解了@Factory的元素的列表。
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
......
}
第三步:判断被注解的是否为类(class)
因为元素 element
可以是 类、包、方法、变量等,根据我们的业务逻辑,被注解的是一个类,所以需要做个判断。
//由于返回的是所有元素,所以还要判断该元素是不是一个类
if (annotatedElement.getKind() != ElementKind.CLASS) {
error(annotatedElement, "Only classes can be annotated with @%s",
Factory.class.getSimpleName());
return true; // 退出处理
}
由于annotatedElement
的元素类型是 ElementKind.CLASS
,所以 annotatedElement
是一个 TypeElement
,所以可以强制转换。因为 new FactoryAnnotatedClass 的时候需要传入 TypeElement
类型的参数。
第四步:判断被注解的是否为符合要求的类
根据我们的业务需要,被注解的类需要满足如下条件
- 必须是个公开类
- 只有一个公开的构造函数
- 不是抽象类
- 继承于特定的类型(例子中我们继承了 Meal 类)
上一步我们把annotatedElement
转化为TypeElement
后构建了FactoryAnnotatedClass
,在FactoryAnnotatedClass
中我们取出了TypeElement
中包含的被注解类相关信息.
private boolean isValidClass(FactoryAnnotatedClass item) {
// 转换为TypeElement, 含有更多特定的方法(就是被@Factory 注解的类的类型)
TypeElement classElement = item.getTypeElement();
//是否是一个公开的类(class 前面的修饰语是否包含 PUBLIC)
if (!classElement.getModifiers().contains(Modifier.PUBLIC)) {
error(classElement, "The class %s is not public.",
classElement.getQualifiedName().toString());
return false;
}
// 检查是否是一个抽象类(被 @Factory 注解的不能是个抽象类)
if (classElement.getModifiers().contains(Modifier.ABSTRACT)) {
error(classElement, "The class %s is abstract. You can't annotate abstract classes with @%",
classElement.getQualifiedName().toString(), Factory.class.getSimpleName());
return false;
}
// 检查继承关系: 必须是@Factory.type()指定的类型子类(获取 Meal TypeElement(类型元素))
TypeElement superClassElement =
elementUtils.getTypeElement(item.getQualifiedFactoryGroupName());
//是否是个接口 interface(这里指 Meal )
if (superClassElement.getKind() == ElementKind.INTERFACE) {
// 检查接口是否实现了
//被 @Factory 注解的类实现的接口是否包含 superClassElement (这里指 Meal)
if (!classElement.getInterfaces().contains(superClassElement.asType())) {
error(classElement, "The class %s annotated with @%s must implement the interface %s",
classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
item.getQualifiedFactoryGroupName());
return false;
}
}
//如果()
else {
// 检查子类 ,classElement 是被@Factory 注解的类
TypeElement currentClass = classElement;
while (true) {
TypeMirror superClassType = currentClass.getSuperclass();
if (superClassType.getKind() == TypeKind.NONE) {
// 到达了基本类型(java.lang.Object), 所以退出
error(classElement, "The class %s annotated with @%s must inherit from %s",
classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
item.getQualifiedFactoryGroupName());
return false;
}
//父类就是要求的父类(这里指 Meal)
if (superClassType.toString().equals(item.getQualifiedFactoryGroupName())) {
// 找到了要求的父类
break;
}
// 在继承树上继续向上搜寻
currentClass = (TypeElement) typeUtils.asElement(superClassType);
}
}
// 检查是否提供了默认公开构造函数(@被 Factory 注解的类是否 有 公开的构造 函数)
for (Element enclosed : classElement.getEnclosedElements()) {
if (enclosed.getKind() == ElementKind.CONSTRUCTOR) {
ExecutableElement constructorElement = (ExecutableElement) enclosed;
//构造函数是公开的,切构造函数参数为0
if (constructorElement.getParameters().size() == 0 && constructorElement.getModifiers()
.contains(Modifier.PUBLIC)) {
// 找到了默认构造函数
return true;
}
}
}
// 没有找到默认构造函数
error(classElement, "The class %s must provide an public empty default constructor",
classElement.getQualifiedName().toString());
return false;
}
第五步:构建 FactoryGroupedClasses 并存入 Map 中
- 使用即将构造为工厂类的接口名称的合法全名构造(本例为
Meal
的合法全名,之后生成的工厂类为 MealFactory)FactoryGroupedClasses
。在FactoryGroupedClasses
中,我们定义了一个Map
类型的全局变量 。通过调用FactoryGroupedClasses
的add
方法,用前面构建的FactoryAnnotatedClass
实例的id
作为key
,FactoryAnnotatedClass
实例作为值存储在该 Map 中.
public void add(FactoryAnnotatedClass toInsert) throws ProcessingException {
/**
*查询 {@link itemsMap } 中是否有该id 的 FactoryAnnotatedClass
* */
FactoryAnnotatedClass existing = itemsMap.get(toInsert.getId());
if (existing != null) {
// Alredy existing
throw new ProcessingException(toInsert.getTypeElement(),
"Conflict: The class %s is annotated with @%s with id ='%s' but %s already uses the same id",
toInsert.getTypeElement().getQualifiedName().toString(), Factory.class.getSimpleName(),
toInsert.getId(), existing.getTypeElement().getQualifiedName().toString());
}
itemsMap.put(toInsert.getId(), toInsert);
}
- 由于可能还有其他工厂类需要构建(比如我们这里构建了MealFactory,但以后可能还需要构建 DrinkFactory),所以在
FactoryProcessor
中我们定义一个Map
类型的全局变量来储存 FactoryGroupedClasses 的实例。
//从缓存Map 里获取
FactoryGroupedClasses factoryClass =
factoryClasses.get(factoryAnnotatedClass.getQualifiedFactoryGroupName());
if (factoryClass == null) {
//Meal 的合法全名
String qualifiedGroupName = factoryAnnotatedClass.getQualifiedFactoryGroupName();
//用 Meal 的合法全名作为参数,生成 FactoryGroupedClasses
factoryClass = new FactoryGroupedClasses(qualifiedGroupName);
//用 Meal 的合法全名作为key FactoryGroupedClasses的实例作为值存入Map
factoryClasses.put(qualifiedGroupName, factoryClass);
}
第六步:构建工厂类
- 遍历 FactoryProcessor 中的Map类型的全局变量,生成不同的工厂类(本例就一个
Meal
工厂类)
for(FactoryGroupedClasses factoryClass : factoryClasses.values()) {
factoryClass.generateCode(elementUtils, filer);
}
-
注意:通过Processor 的源码,我们知道,虽然注解处理器只会初始化一次。但
process()
可能会多次调用。在每个处理循环中,注解处理器会去处理存在该注解的所有文件,包括先前处理循环中生成的文件(这些文件可能也包含注解)。如果factoryClasses
数据没有清空,会重复生成代码,此时则会报错。所以在每个处理循环中需要清空map。但是具体会执行几次,无法确认。可以通过RoundEnvironment,的RoundEnvironment.processingOver() 获知是否到了最后一个处理周期了。
- 调用 factoryClass.generateCode 生成代码。
public void generateCode(Elements elementUtils, Filer filer) throws IOException {
//通过工厂接口的合法全类名获取到工厂接口的 TypeElement(本例的工厂为 Meal)
TypeElement superClassName = elementUtils.getTypeElement(qualifiedClassName);
//获取生成的class 名称(本例生成的为 MealFactory)
String factoryClassName = superClassName.getSimpleName() + SUFFIX;
//获取包元素
PackageElement pkg = elementUtils.getPackageOf(superClassName);
//获取包名
String packageName = pkg.isUnnamed() ? null : pkg.getQualifiedName().toString();
//生成一个方法名:create,参数:String 类型 叫id,返回值
MethodSpec.Builder method = MethodSpec.methodBuilder("create")
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "id")
.returns(TypeName.get(superClassName.asType()));
// check if id is null(生成判断id 是否为空的判断逻辑)
method.beginControlFlow("if (id == null)")
.addStatement("throw new IllegalArgumentException($S)", "id is null!")
.endControlFlow();
// Generate items map
//遍历被注解的类的封装类 FactoryAnnotatedClass
for (FactoryAnnotatedClass item : itemsMap.values()) {
method.beginControlFlow("if ($S.equals(id))", item.getId())
.addStatement("return new $L()", item.getTypeElement().getQualifiedName().toString())
.endControlFlow();
}
method.addStatement("throw new IllegalArgumentException($S + id)", "Unknown id = ");
TypeSpec typeSpec = TypeSpec.classBuilder(factoryClassName).addMethod(method.build()).build();
// Write file
JavaFile.builder(packageName, typeSpec).build().writeTo(filer);
}
generateCode()
方法中使用 Square javapoet
库生成代码,当然也可以使用拼接的方式生成代码。但使用 javapoet
会更加便利。
生成代码
现在我们可以使用我们的注解,生成我们想要的代码了。在Terminal
执行
./gradlew build
,等待构建成功,即可。那生产的源码在哪里呢?
打开看看和我们自己写的工厂类方法一摸一样。好了,以后我们都像大神了,哈哈。美梦.jpg
怎么用?
其实感觉这都不用讲了,像调正常代码一样调用就可以了。
public class PizzaStore {
public static void main(String[] args) {
MealFactory mealFactory = new MealFactory();
Meal meal = mealFactory.create(readPizzaNameFromConsole());
System.out.println("Bill: $" + meal.getPrice());
}
private static String readPizzaNameFromConsole() {
Scanner s = new Scanner(System.in);
System.out.println("请输入披萨名称:");
String lin = "";
lin = s.nextLine();
System.out.println("读取披萨名称结束!!");
return lin;
}
}
嗯,就是这么调用,如果你把我代码clone
下来了,找到这个类应该就可以跑下,试一试。好了这篇有点长,看累了就休息会吧!github 地址
七:参考链接
另外感谢下面这会大神的分享,膜拜!