apt 根据注解,编译时生成代码

时间:2022-12-25 10:42:23

apt:

@Retention后面的值,设置的为CLASS,说明就是编译时动态处理的。一般这类注解会在编译的时候,根据注解标识,动态生成一些类或者生成一些xml都可以,在运行时期,这类注解是没有的~~会依靠动态生成的类做一些操作,因为没有反射,效率和直接调用方法没什么区别~~~

RUNTIME, 说明就是运行时动态处理,这个大家见得应该最多,在运行时拿到类的Class对象,然后遍历其方法、变量,判断有无注解声明,然后做一些事情。

SOURCE,标记一些信息,这么说可能太抽象,那么我说,你见过@Override、@SuppressWarnings等,这类注解就是用于标识,可以用作一些检验 

 

@Target表示该注解可以用于什么地方,可能的类型TYPE(类),FIELD(成员变量)

 1 public enum ElementType {  
2 /**
3 * Class, interface or enum declaration.
4 */
5 TYPE,
6 /**
7 * Field declaration.
8 */
9 FIELD,
10 /**
11 * Method declaration.
12 */
13 METHOD,
14 /**
15 * Parameter declaration.
16 */
17 PARAMETER,
18 /**
19 * Constructor declaration.
20 */
21 CONSTRUCTOR,
22 /**
23 * Local variable declaration.
24 */
25 LOCAL_VARIABLE,
26 /**
27 * Annotation type declaration.
28 */
29 ANNOTATION_TYPE,
30 /**
31 * Package declaration.
32 */
33 PACKAGE
34 }

TypeElement  :类

JavaPoet源码初探

TypeSpec是类型元素的抽象,通过Kind枚举定义class、interface、enum、annotation四种类型。

MethodSpec代表方法的抽象。

1     // 元素操作的辅助类
2 Elements elementUtils;
3 //文件相关的辅助类
4 private Filer mFiler;
5 //日志相关的辅助类
6 private Messager mMessager;

1、先添加两个java library,一个annotation,一个compiler:

apt 根据注解,编译时生成代码

annotation library:用于放置注解。

build.gradle文件:注意属于java library,不使用android library。

1  apply plugin: 'java'
2
3 sourceCompatibility = 1.7
4 targetCompatibility = 1.7
5 dependencies {
6 compile fileTree(dir: 'libs', include: ['*.jar'])
7 }

compile library:用于放置根据注解生成代码类。

build.gradle文件:注意属于java library,不使用android library。

 1 apply plugin: 'java'
2
3 sourceCompatibility = 1.7
4 targetCompatibility = 1.7
5 dependencies {
6 compile fileTree(dir: 'libs', include: ['*.jar'])
7
8 compile 'com.google.auto.service:auto-service:1.0-rc2'
9 compile 'com.squareup:javapoet:1.7.0'
10 <!-- 引用annotation library -->
11 compile project(':annotation')
12 }

app module:android app

build.gradle文件:

 1 apply plugin: 'com.android.application'
2 apply plugin: 'com.neenbedankt.android-apt'
3
4 android {
5 compileSdkVersion 23
6 buildToolsVersion "23.0.2"
7
8 defaultConfig {
9 applicationId "com.example.aptdemo"
10 minSdkVersion 15
11 targetSdkVersion 23
12 versionCode 1
13 versionName "1.0"
14 }
15 buildTypes {
16 release {
17 minifyEnabled false
18 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 }
20 }
21 }
22
23 dependencies {
24 compile fileTree(dir: 'libs', include: ['*.jar'])
25 testCompile 'junit:junit:4.12'
26 compile 'com.android.support:appcompat-v7:24.0.0'
27
28 compile project(':annotation')
29 apt project(':compiler')
30 }

官方有一个列子的:生成一个java类,然后java 类里面有个main函数

// 注解类 (目录:annotation library)

package com.example;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Test {
}

// apt生成类 (目录:compiler library)

apt 根据注解,编译时生成代码apt 根据注解,编译时生成代码
 1 package com.example;
2
3 import com.google.auto.service.AutoService;
4 import com.squareup.javapoet.JavaFile;
5 import com.squareup.javapoet.MethodSpec;
6 import com.squareup.javapoet.TypeSpec;
7 import java.io.IOException;
8 import java.util.Collections;
9 import java.util.Set;
10 import javax.annotation.processing.AbstractProcessor;
11 import javax.annotation.processing.Processor;
12 import javax.annotation.processing.RoundEnvironment;
13 import javax.lang.model.SourceVersion;
14 import javax.lang.model.element.Modifier;
15 import javax.lang.model.element.TypeElement;
16
17 @AutoService(Processor.class)
18 public class TestProcessor extends AbstractProcessor {
19
20 /***
21 package com.example.helloworld;
22
23 public final class HelloWorld {
24 public static void main(String[] args) {
25 System.out.println("Hello, JavaPoet!");
26 }
27 }
28 */
29
30 @Override
31 public Set<String> getSupportedAnnotationTypes() {
32 return Collections.singleton(Test.class.getCanonicalName());
33 }
34
35 @Override
36 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
37
38 MethodSpec main = MethodSpec.methodBuilder("main")
39 .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
40 .returns(void.class)
41 .addParameter(String[].class, "args")
42 .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
43 .build();
44
45 TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
46 .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
47 .addMethod(main)
48 .build();
49
50 JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
51 .build();
52
53 try {
54 javaFile.writeTo(processingEnv.getFiler());
55 } catch (IOException e) {
56 e.printStackTrace();
57 }
58
59 return false;
60 }
61
62 @Override
63 public SourceVersion getSupportedSourceVersion() {
64 return SourceVersion.RELEASE_7;
65 }
66 }
官方例子

// 使用:

在随意一个类上面加上@Test,编译时,代码就会生成。

下面就是简单的view注解生成代码:

// 注解类 (目录:annotation library)

1、activity 注解 :作用于类,所以目标类型是类。

1 @Target(ElementType.TYPE)
2 @Retention(RetentionPolicy.CLASS)
3 public @interface DIActivity {
4 }

2、view 注解:作用于字段,所以目标类型是字段。

1 @Target(ElementType.FIELD)
2 @Retention(RetentionPolicy.RUNTIME)
3 public @interface DIView {
4
5 int value() default 0;
6 }

3、onClick 注解:作用于方法,所以目标类型是方法。

1 @Target(ElementType.METHOD)
2 @Retention(RetentionPolicy.RUNTIME)
3 public @interface DIOnClick {
4
5 int[] value() default 0;
6 }

// 使用工具类:

ViewInject:用于统一activity 使用方法,注入。

 1 public class ViewInject {
2
3 public final static void bind(Activity activity){
4
5 String className = activity.getClass().getName();
6 try {
7 Class<?> aClass = Class.forName(className + "$ViewInject");
8 Inject inject = (Inject) aClass.newInstance();
9 inject.bindView(activity);
10 } catch (Exception e){
11 e.printStackTrace();
12 }
13 }
14 }

Inject:用于apt生成类继承,统一父类。

1 public interface Inject<T> {
2
3 void bindView(T host);
4 }

// apt生成类 (目录:compiler library)

DIProcessor:

apt 根据注解,编译时生成代码apt 根据注解,编译时生成代码
  1 @AutoService(Processor.class)
2 public class DIProcessor extends AbstractProcessor {
3
4 // 元素操作的辅助类
5 Elements elementUtils;
6 //文件相关的辅助类
7 private Filer mFiler;
8 //日志相关的辅助类
9 private Messager mMessager;
10
11 @Override
12 public synchronized void init(ProcessingEnvironment processingEnv) {
13 super.init(processingEnv);
14 // 元素操作的辅助类
15 elementUtils = processingEnv.getElementUtils();
16 mFiler = processingEnv.getFiler();
17 mMessager = processingEnv.getMessager();
18 }
19
20 /**
21 * 指定哪些注解应该被注解处理器注册
22 * @return
23 */
24 @Override
25 public Set<String> getSupportedAnnotationTypes() {
26 /**
27 * Set<String> types = new LinkedHashSet<>();
28 types.add(BindView.class.getCanonicalName());
29 types.add(OnClick.class.getCanonicalName());
30 return types;
31 * */
32 // 规定需要处理的注解
33 return Collections.singleton(DIActivity.class.getCanonicalName());
34 }
35
36 @Override
37 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
38
39 Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(DIActivity.class);
40
41 for(Element element : elements){
42
43 // 判断是否Class
44 TypeElement typeElement = (TypeElement) element;
45
46 List<? extends Element> members = elementUtils.getAllMembers(typeElement);
47
48 MethodSpec.Builder bindViewMethod = MethodSpec.methodBuilder("bindView")
49 .addModifiers(Modifier.PUBLIC)
50 .returns(TypeName.VOID)
51 .addParameter(ClassName.get(typeElement.asType()), "activity", Modifier.FINAL);
52
53 bindViewMethod.addStatement("mContext = activity");
54
55 MethodSpec.Builder onClickMethod = MethodSpec.methodBuilder("onClick")
56 .addAnnotation(Override.class)
57 .addModifiers(Modifier.PUBLIC)
58 .returns(TypeName.VOID)
59 .addParameter(ClassName.get("android.view", "View"), "view");
60
61 // TypeSpec.Builder listener = TypeSpec.anonymousClassBuilder("")
62 // .addSuperinterface(ClassName.get("android.view", "View", "OnClickListener"));
63
64 mMessager.printMessage(Diagnostic.Kind.NOTE, "非常厉害");
65
66 // bindViewMethod.addStatement("View.OnClickListener listener = null");
67 onClickMethod.addCode("switch(view.getId()) {");
68
69 for (Element item : members) {
70 // handler the findViewById
71 DIView diView = item.getAnnotation(DIView.class);
72 if (diView != null){
73
74 bindViewMethod.addStatement(String.format("activity.%s = (%s) activity.findViewById(%s)"
75 ,item.getSimpleName(), ClassName.get(item.asType()).toString(), diView.value()));
76 continue;
77 }
78
79 // handler the setOnViewClick
80 DIOnClick diOnClick = item.getAnnotation(DIOnClick.class);
81 if(diOnClick != null) {
82
83 if(diOnClick.value().length > 1) {
84 for (int index = 0; index < diOnClick.value().length; index++) {
85 onClickMethod.addCode("case $L: ", diOnClick.value()[index]);
86
87 bindViewMethod.addStatement("activity.findViewById($L).setOnClickListener(this)", diOnClick.value()[index]);
88 }
89
90 onClickMethod.addStatement("mContext.$N()", item.getSimpleName());
91
92 onClickMethod.addStatement("break");
93 }
94
95 if(diOnClick.value().length == 1) {
96 onClickMethod.addCode("case $L: ", diOnClick.value()[0]);
97 onClickMethod.addStatement("mContext.$N()", item.getSimpleName());
98 onClickMethod.addStatement("break");
99
100 bindViewMethod.addStatement("activity.findViewById($L).setOnClickListener(this)", diOnClick.value()[0]);
101 }
102 continue;
103 }
104 }
105
106 onClickMethod.addStatement("}");
107
108 // TypeSpec onClickListener = listener.addMethod(onClickMethod.build()).build();
109 // bindViewMethod.addStatement("listener = $L ", onClickListener);
110
111 List<MethodSpec> methodSpecs = new ArrayList<>();
112 methodSpecs.add(bindViewMethod.build());
113 methodSpecs.add(onClickMethod.build());
114
115 List<TypeName> typeNames = new ArrayList<>();
116 typeNames.add(ClassName.get("android.view", "View", "OnClickListener"));
117 typeNames.add(ParameterizedTypeName.get(ClassName.get("com.example.charles.aptdemo.inject", "Inject"), TypeName.get(typeElement.asType())));
118
119 FieldSpec fieldSpec = FieldSpec.builder(TypeName.get(typeElement.asType()), "mContext").build();
120
121 TypeSpec typeSpec = TypeSpec.classBuilder(element.getSimpleName() + "$ViewInject")
122 // .superclass(TypeName.get(typeElement.asType()))
123 .addSuperinterfaces(typeNames)
124 .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
125 .addMethods(methodSpecs)
126 .addField(fieldSpec)
127 .build();
128
129 JavaFile javaFile = JavaFile.builder(getPackageName(typeElement), typeSpec).build();
130
131 try {
132 javaFile.writeTo(processingEnv.getFiler());
133 } catch (IOException e) {
134 e.printStackTrace();
135 }
136
137 mMessager.printMessage(Diagnostic.Kind.NOTE, "完成");
138 }
139
140 return true;
141 }
142
143 private String getPackageName(TypeElement type) {
144 return elementUtils.getPackageOf(type).getQualifiedName().toString();
145 }
146
147 /**
148 * 指定使用的 Java 版本。通常返回SourceVersion.latestSupported()。
149 * @return
150 */
151 @Override
152 public SourceVersion getSupportedSourceVersion() {
153 return SourceVersion.RELEASE_7;
154 }
155 }
DIProcessor

// 使用方式:

 1 @DIActivity
2 public class MainActivity extends AppCompatActivity {
3
4 @DIView(R.id.text)
5 public TextView textView;
6
7 @Override
8 protected void onCreate(Bundle savedInstanceState) {
9 super.onCreate(savedInstanceState);
10 setContentView(R.layout.activity_main);
11
12 ViewInject.bind(this);
13
14 textView.setText("我不是这样子的");
15 }
16
17 @DIOnClick({R.id.btn_change_text, R.id.btn_change})
18 public void changeTextView(){
19 textView.setText("我被点击了");
20 }
21
22 @DIOnClick(R.id.btn_change_new)
23 public void changeNew(){
24 textView.setText("new");
25 }
26 }

关于javapoet:

JavaPoet 是一个用来生成 .java源文件的Java API。

下列文章为apt

Android注解-编译时生成代码 (APT)

下列文章为反射:

 Android 打造编译时注解解析框架 这只是一个开始

Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)

Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (下)