Java注解实战之APT构建模块化的第一步
前言
APT的学习要花点时间去掌握和实践的,短时间内只能掌握知识点,更多的是在实战中去实践。其实,APT就是一种工具而已,只要用多了,自然就会熟练了,不过要想实践之前,还是必须把基础知识学好才能实战进入开发。文章会从基础用例讲解知识点,然后再通过实战进行实践
APT简介
APT(Annotation Processing Tool)是一种处理注解的工具,它会对源代码中的注解进行额外的处理,比如在编译时生成一些重复性操作的Java代码,或者不需要程序员去关心的Java代码等。
在使用APT的过程中会涉及到下面两个第三方库的使用
- AutoService:这个库的主要作用是注册注解,并对其生成META-INF的配置信息
- JavaPoet:这个库的主要作用是帮助我们通过类调用的形式来生成Java代码
APT主要过程包括初始化过程和注解处理过程
- 初始化过程:获取APT提供的工具类,为后面的注解处理提供帮助
- 注解处理过程:获取注解的元素,对元素进行额外处理,可用JavaPoet生成Java代码
APT流程
1、定义注解
该注解是可以在我们的项目中使用到的,且规定为注解元素的类型为Type,和在编译时生效
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ModuleWrapper { }
2、定义Processor
Processor会在编译期对注解进行解析,取出对应的元素进行处理。至于AutoService则是固定的写法,加个注解即可
@AutoService(Processor.class) public class ModuleProcessor extends AbstractProcessor { private Filer filerUtils; // 文件写入 private Elements elementUtils; // 操作Element工具类 private Messager messagerUtils; // Log 日志 private Map<String, String> options; // 额外配置参数 @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); filerUtils = processingEnvironment.getFiler(); elementUtils = processingEnvironment.getElementUtils(); messagerUtils = processingEnvironment.getMessager(); options = processingEnvironment.getOptions(); } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); types.add(ModuleWrapper.class.getCanonicalName()); return types; } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { initModuleMap(roundEnvironment); return false; } private void initModuleMap(RoundEnvironment roundEnv) { //获取对应的注解元素 Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(ModuleWrapper.class); for (Element element : set) { //如果是个类 if (element.getKind() == ElementKind.CLASS) { //获取类名 String clzName = element.getSimpleName().toString(); //对元素进行处理,可用javapoet生成Java代码 ...... } else { messagerUtils.printMessage(Diagnostic.Kind.NOTE, "only support class"); } } } }
3、AbstractProcessor实现方法介绍
init():初始化过程,可通过初始化方法获取各种工具类
process():注解处理过程,可通过获取注解元素后,对注解元素进行额外处理
getSupportedAnnotationTypes():获取需要解析的注解类型
APT知识点
1、初始化介绍
APT初始化阶段为init方法的调用,我们可以使用ProcessingEnvironment获取一些实用类以及获取选项参数等
2、Element介绍
Element是操作元素最主要的类,可通过getElementsAnnotatedWith获取Element的元素,经过getKind()判断元素的类型后,可强制转换成对应的类型,在对应的类型中有着不同的方法可以调用
ElementKind为元素的类型,元素的类型判断不需要用instanceof去判断,而应该通过getKind()去判断对应的类型
3、TypeMirror介绍
TypeMirror是一个接口,表示Java编程语言中的类型。这些类型包括基本类型、引用类型、数组类型、类型变量和null类型等等
TypeKind为类型的属性,类型的属性判断不需要用instanceof去判断,而应该通过getKind()去判断对应的属性
这里需要注意的是,如果我们通过注解去获取Class类型的值,如果获取的Class未被编译,则会抛出MirroredTypeException异常,此时我们需要通过try-catch语句在catch里去获取我们所需要的类元素
try { annotation.value();//如果value为Class类型则会报异常 } catch (MirroredTypeException mte) { DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror(); TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();//通过异常去获取类元素 }
4、Filer介绍
Filer接口支持通过注解处理器创建新文件。可以创建三种文件类型:源文件、类文件和辅助资源文件
5、Messager介绍
Messager接口提供注解处理器用来报告错误消息、警告和其他通知的方式
6、Options介绍
通过getOptions()方法获取选项参数,在gradle文件中配置选项参数值
android { defaultConfig { javaCompileOptions { annotationProcessorOptions { arguments = [ version : '1.0.0' ] } } } }
通过ProcessingEnvironment去获取对应的参数
processingEnvironment.getOptions().get("version");
7、获取注解元素
通过RoundEnvironment接口去获取注解元素,通过JavaPoet生成Java代码
APT实战
下面通过APT的实战,进行对项目的模块化划分
1、项目结构
- 创建Module,名为annotation,放置我们的注解类
- 创建Module,名为compiler,放置我们的注解处理类
- 主工程则直接依赖annotation和compiler注意事项:创建Module的时候,需要选择java Lib,而不是Android Lib
2、Gradle配置
annotation的Module必须声明Java编译版本
apply plugin: 'java-library' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) } sourceCompatibility = "1.7" targetCompatibility = "1.7"
compiler的Module必须声明Java编译版本,且依赖于annotation和导入我们所需的库
apply plugin: 'java-library' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':annotation') implementation 'com.google.auto.service:auto-service:1.0-rc2' implementation 'com.squareup:javapoet:1.7.0' } sourceCompatibility = "1.7" targetCompatibility = "1.7"
主工程必须通过依赖annotaion和compiler,由于我们只是在编译期生效,可用annotationProcessor
implementation project(':annotation') annotationProcessor project(':compiler')
注意事项:定义编译的jdk版本为1.7
3、定义注解
ModuleWrapper注解,表示需要加载的模块
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ModuleWrapper { }
IModule接口,表示当前类是一个模块类
public interface IModule { String getModuleName(); }
4、定义Processor
@AutoService(Processor.class) public class ModuleProcessor extends AbstractProcessor { private Map<String, ModuleInfo> moduleMaps = new HashMap<>(); private Filer filerUtils; // 文件写入 private Elements elementUtils; // 操作Element 的工具类 private Messager messagerUtils; // Log 日志 private Map<String, String> options; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); filerUtils = processingEnvironment.getFiler(); elementUtils = processingEnvironment.getElementUtils(); messagerUtils = processingEnvironment.getMessager(); options = processingEnvironment.getOptions(); } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); types.add(ModuleWrapper.class.getCanonicalName()); return types; } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { try { initModuleMap(roundEnvironment); createModuleMap(); createModuleConstant(); } catch (IOException e) { e.printStackTrace(); } return false; } /** * 通过注解元素获取组件实体 * * @param roundEnv */ private void initModuleMap(RoundEnvironment roundEnv) { Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(ModuleWrapper.class); for (Element element : set) { if (element.getKind() == ElementKind.CLASS) { String clzName = element.getSimpleName().toString(); if (moduleMaps.get(clzName) == null) { ModuleInfo info = new ModuleInfo(elementUtils, (TypeElement) element); moduleMaps.put(clzName, info); } } else { messagerUtils.printMessage(Diagnostic.Kind.NOTE, "only support class"); } } } /** * 创建组件管理者 * * @throws IOException */ private void createModuleMap() throws IOException { FieldSpec fieldSpec = FieldSpec .builder(ParameterizedTypeName.get(HashMap.class, String.class, IModule.class) , "moduleMap", Modifier.PRIVATE) .initializer("new HashMap<>()") .build(); CodeBlock.Builder codeBlock = CodeBlock.builder(); for (String key : moduleMaps.keySet()) { ModuleInfo info = moduleMaps.get(key); codeBlock.addStatement("moduleMap.put($S ,new $T())", info.getFullClassName(), ClassName.get(info.packageName, info.className)); } MethodSpec initMethod = MethodSpec.methodBuilder("init") .addModifiers(Modifier.PUBLIC) .addCode(codeBlock.build()) .returns(TypeName.VOID) .build(); MethodSpec getMethod = MethodSpec.methodBuilder("get") .addModifiers(Modifier.PUBLIC) .addParameter(String.class, "cls") .addStatement("return moduleMap.get(cls)") .returns(IModule.class) .build(); ArrayList<MethodSpec> methods = new ArrayList<>(); methods.add(initMethod); methods.add(getMethod); TypeSpec moduleFactory = TypeSpec.classBuilder("ModuleFactory") .addModifiers(Modifier.PUBLIC) .addMethods(methods) .addField(fieldSpec) .build(); JavaFile javaFile = JavaFile.builder("com.hensen.compiler.processor", moduleFactory) .build(); javaFile.writeTo(filerUtils); } /** * 创建组件常量 * * @throws IOException */ private void createModuleConstant() throws IOException { TypeSpec.Builder moduleConstant = TypeSpec.classBuilder("ModuleConstant") .addModifiers(Modifier.PUBLIC); for (String key : moduleMaps.keySet()) { ModuleInfo info = moduleMaps.get(key); FieldSpec fieldSpec = FieldSpec.builder(String.class, info.className) .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) .initializer("$S", info.getFullClassName()) .build(); moduleConstant.addField(fieldSpec); } JavaFile javaFile = JavaFile.builder("com.hensen.compiler.processor", moduleConstant.build()) .build(); javaFile.writeTo(filerUtils); } }
5、组件实体
组件实体保存着组件的信息
public class ModuleInfo { public String packageName; public String className; public ModuleInfo(Elements elementUtils, TypeElement typeElement) { packageName = getPackageName(elementUtils, typeElement); className = getClassName(typeElement, packageName); } public String getClassName(TypeElement type, String packageName) { int packageLen = packageName.length() + 1; return type.getQualifiedName().toString().substring(packageLen) .replace('.', '$'); } public String getPackageName(Elements elementUtils, TypeElement classElement) { PackageElement packageElement = elementUtils.getPackageOf(classElement); return packageElement.getQualifiedName().toString(); } public String getFullClassName() { return packageName + "." + className; } public String getClassName(){ return className; } }
6、注解使用
分别创建出礼物模块和聊天模块,聊天模块增加发消息的方法
@ModuleWrapper public class ChatModule implements IModule{ @Override public String getModuleName() { return "ChatModule"; } public void sendMessage() { Log.i("TAG", "Hi"); } } @ModuleWrapper public class GiftModule implements IModule{ @Override public String getModuleName() { return "GiftModule"; } }
7、生成代码
在生成代码之前需要gradle build,查看生成代码。当然对于模块来说,不仅有get()、还有add()、remove()等其他扩展功能,具体的就留给大家去操作
public class ModuleFactory { private HashMap<String, IModule> moduleMap = new HashMap<>(); public void init() { moduleMap.put("com.hensen.geneapt.GiftModule" ,new GiftModule()); moduleMap.put("com.hensen.geneapt.ChatModule" ,new ChatModule()); } public IModule get(String cls) { return moduleMap.get(cls); } } public class ModuleConstant { public static final String GiftModule = "com.hensen.geneapt.GiftModule"; public static final String ChatModule = "com.hensen.geneapt.ChatModule"; }
8、组件使用
初始化组件的加载,通过工厂获取对应的模块进行操作
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ModuleFactory moduleFactory = new ModuleFactory(); moduleFactory.init(); ChatModule chatModule = (ChatModule) moduleFactory.get(ModuleConstant.ChatModule); chatModule.sendMessage(); } }
- maven构建的模块化的JavaWeb工程
- Java大数据实战 Storm构建实时流处理
- 【java】itoo项目实战促学之SpringMVC注解_@RequestMapping
- java注解详解(注解项目实战)
- 文件函数Django1.5实战第一步之搭建环境,创建工程,运行开发服务器Strut2教程-java教程
- 转:java 注解 实战剖析
- 【JAVA并发编程实战】5、构建高效且可伸缩的结果缓存
- java并发编程实战学习(3)--基础构建模块
- java实战(七)--------myeclipse构建Spring的开发环境 spring框架配置
- Hibernate4实战之Hibernate4注解零配置 浏览(88173)|评论(0) 交流分类:Java|笔记分类: 未分类 @Entity,注册在类头上,将一个类声明为一个实体bean
- java实战(七)--------myeclipse构建Spring的开发环境 spring框架配置
- Docker10-实战-构建Java Web运行环境
- Apache Ant对决Make:实战Java构建工具
- Java元注解的实战应用-实体类注解封装(下)
- maven构建的模块化的JavaWeb工程
- Java并发编程实战:并发基础构建模块
- Apache Ant对决Make:实战Java构建工具
- Java 开发基于Zookeeper,Spring,vue.js的高并发多用户模块化微信商城系统(三) 构建高可用MySQL服务
- java 注解 实战剖析(转载)
- 教你一步一步构建java技术栈--企业级架构方案实战