注解、AOP、APT

注解

注解和反射的区别

反射:JAVA 反射机制是在运行状态中,对于任意一个类 (class 文件),都能够知道这个类的所有属性和方法。
注解:降低项目的耦合度;自动完成一些规律性的代码;自动生成 java 代码,减轻开发者的工作量。
而注解需要用到反射:
定义注解,使用注解,读取注解(用到反射)

注解和反射效率问题

反射先 new 类 class,然后在从类里面 new 对象。Class.getMethod (…)还要查找所有的方法。
而注解编译期间就完成了注解的反射工作, jvm 只是读取。

反射的缺点

不安全
编译器没法对反射相关的代码做优化
慢的原因还有安全检查,访问控制等。比如说这个方法你能不能获得,能不能执行等,你传进的参数的类型检查等。比如说在使用反射调用方法的时候,传进的参数需要检查是否符合方法参数类型要求吧?
unknown_filename|260

注解的用法

获取类的注解
URLBuilder. Path path =paramEntity.getClass (). getAnnotation (URLBuilder. Path. class);

1
2
3
4
5
6
7
Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();
this.parameterTypes = method.getGenericParameterTypes();
this.parameterAnnotationsArray = method.getParameterAnnotations();
}

获取方法的注解

Method.getAnnotations (); 是获取方法上面对应的注解。
method.getParameterAnnotations ();获取的是方法参数上面的注解,是一个二维数组,第一个维度代表的是方法参数对应的下标,比如,一个方法有3个参数,那0代表第一个参数,1代表第二个参数,2代表第三个参数。

获取泛型参数

method.getGenericParameterTypes (); 获取的是方法参数的类型,里面带有实际的参数类型。

生命周期

@Retention注解保留的生命周期

  1. SOURCE: 在源文件中有效(即源文件保留)而被编译器丢弃,运行的时候不会在 class 文件里。(编译器在,运行时没有)   
  2. CLASS: 在 class 文件中有效(即 class 保留)    
  3. RUNTIME: 在运行时有效(即运行时保留) 这个范围更大,写这个一般不会有什么问题
    生命周期:SOURCE < CLASS < RUNTIME

使用场景

  1. 一般如果需要在运行时去动态获取注解信息,用 RUNTIME 注解,Retrofit
  2. 要在编译时进行一些预处理操作,如 ButterKnife,用 CLASS 注解。注解会在 class 文件中存在,但是在运行时会被丢弃
  3. 做一些检查性的操作,如@Override,用 SOURCE 源码注解。注解仅存在源码级别,在编译的时候丢弃该注解

@Target:注解对象的作用范围。

创建一个注解遵循: public @interface 注解名 {方法参数} (运行时注入)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface getViewTo {
int value() default -1;
}

//绑定一个View (View不能为private 或者static)否则就会通过反射去获取
@BindView(R.id.textview)

public class Binding {
public static void bind(Activity activity) {
for (Field field : activity.getClass().getDeclaredFields()) {
BindView bindView = field.getAnnotation(BindView.class);
if (bindView != null) {
try {
field.setAccessible(true);
field.set(activity, activity.findViewById(bindView.value()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}

编译时注解与运行时注解区别?

保留阶段: 编译时注解在编译期间被存储在. Class 字节码文件中,运行时无法访问。而运行时注解保留到运行时,可在运行时访问。

原理: 编译时注解通过 APT、AbstractProcessor 等机制工作,而运行时注解是 Java 反射机制
性能: 运行时注解由于使用 Java 反射,对性能有影响。编译时注解对性能没影响。
立物: 运行时注解只需自定义注解处理器即可,不会产生其他文件。而编译时注解通常会立生新的 Java 源文件
用途: 运行时注解通常在运行时被访问,例如 Retrofit 的运行时注解,需要用的时候才用到。编译时注解如@Override、 @SuppressWarnings 等,在编译期间被处理

AOP(思想)

正如面向对象编程是对常见问题的模块化一样,面向切面编程是对横向的同一问题进行模块化,比如在某个包下的所有类中的某一类方法中都需要解决一个相似的问题,可以通过 AOP 的编程方式对此进行模块化封装,统一解决

AOP 编程的具体使用场景
日志记录
持久化
行为监测
数据验证
缓存

比如埋点,记录方法执行的时长。可以定义注解。aop 可以过滤所有被”这个注解”标记的方法和构造器。然后可以根据他提供的方法(注解),将我们想要埋点的日志插入到前面或后面。

aop 三大工具

unknown_filename.5|600

APT:
解析注解生成新的文件,这其中我们最常见到的了,难度也最低。定义编译期的注解,再通过继承 Proccesor 实现代码生成逻辑,实现了编译期生成代码的逻辑。(DataBinding, Dagger2, ButterKnife, EventBus3)

AspectJ:
编译期和加载时代码方法等前后注入(代表框架: Hugo (Jake Wharton))

javassist:
这层还有 asm,在 class 文件被转化为 dex 文件之前去修改。( [[Gradle插件]] )

APT

Annotation Processing Tool (APT),注解处理工具,javac 中用于编译时扫描和解析 Java 注解的工具。可以自己定义。
在编译阶段执行的,它的原理就是读入 Java 源代码,解析注解,然后生成新的 Java 代码,新生成的 Java 代码最后被编译成 Java 字节码,注解解析器不能改变读入的 Java 类,比如不能加入或删除 Java 方法

Java API 已经提供了扫描源码并解析注解的框架,开发者可以通过继承 AbstractProcessor 类来实现自己的注解处理逻辑。

APT 的原理:在注解了某些代码元素(如字段、函数、类等)后,在编译时编译器会检查 AbstractProcessor 的子类,并且自动调用其 process() 方法,然后将添加了指定注解的所有代码元素作为参数传递给该方法,开发者在根据注解元素在编译期输出对应的 Java 代码。

APT 负责处理编译时注解,JavaPoet 用于生成 Java 代码,AutoService 负责注册注解处理器。

AutoServcie

以前要注册注解处理器要在 module 的 META_INFO 目录新建 services 目录,并创建一个名为 Java.annotation.processing.Processor 的文件,然后在文件中写入要注册的注解处理器的全名。

后来 Google 推出了 AutoService 注解库来实现注册注解处理器的注册,通过在注解处理器上加上 @AutoService(Processor.class) 注解,即可在编译时生成 META_INFO 信息

1
2
3
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
}

JavaPoet

javaPoet使用指南

eventbus如何用的

JavaPoet比普通方法 (手动字符串拼接)创建更加方便
APT 传统方式(手动字符串拼接)—》生成 java 文件
APT JavaPoet 方式–》生成 Java 文件

Butterknife_Framework_20200225 项目原生
New_Modular_CustomARouter 项目 javaPoet

ButterKnife 的实现

分为四步:

  1. 定义注解
  2. 注解处理器处理注解
  3. 生成 Java 文件
  4. 引入
定义注解
1
2
3
4
5
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
注解处理器处理注解、生成 Java 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {

private Elements mElementsUtils;
private Types mTypesUtils;
private Filter mFilter;
private Messager mMessager;

/**
* 初始化方法
* 可以初始化一些给注解处理器使用的工具类
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mElementsUtils = processingEnvironment.getElementUtils();
}

/**
* 指定目标注解对象
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> hashSet = new HashSet<>();
hashSet.add(BindView.class.getCanonicalName());
return hashSet;
}

/**
* 指定使用的 Java 版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

/**
* 处理注解
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//获取所有包含 BindView 注解的元素
Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);
Map<TypeElement, Map<Integer, VariableElement>> typeElementMapHashMap = new HashMap<>();
for (Element element : elementSet) {
//因为 BindView 的作用对象是 FIELD,因此 element 可以直接转化为 VariableElement
VariableElement variableElement = (VariableElement) element;
//getEnclosingElement 方法返回封装此 Element 的最里层元素
//如果 Element 直接封装在另一个元素的声明中,则返回该封装元素
//此处表示的即是 Activity 类对象
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
Map<Integer, VariableElement> variableElementMap = typeElementMapHashMap.get(typeElement);
if (variableElementMap == null) {
variableElementMap = new HashMap<>();
typeElementMapHashMap.put(typeElement, variableElementMap);
}
//获取注解的值,即 ViewId
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
int viewId = bindAnnotation.value();
variableElementMap.put(viewId, variableElement);
}
for (TypeElement key : typeElementMapHashMap.keySet()) {
Map<Integer, VariableElement> elementMap = typeElementMapHashMap.get(key);
String packageName = ElementUtil.getPackageName(mElementsUtils, key);

JavaFile javaFile = JavaFile.builder(packageName, generateCodeByPoet(key, elementMap)).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}

/**
* 生成 Java 类
*
* @param typeElement 注解对象的上层元素对象,即 Activity 对象
* @param variableElementMap Activity 包含的注解对象以及注解的目标对象
* @return
*/
private TypeSpec generateCodeByPoet(TypeElement typeElement, Map<Integer, VariableElement> variableElementMap) {
//自动生成的文件以 Activity 名 + ViewBinding 进行命名
return TypeSpec.classBuilder(ElementUtil.getEnclosingClassName(typeElement) + "ViewBinding")
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethodByPoet(typeElement, variableElementMap))
.build();
}

/**
* 生成方法
*
* @param typeElement 注解对象上层元素对象,即 Activity 对象
* @param variableElementMap Activity 包含的注解对象以及注解的目标对象
* @return
*/
private MethodSpec generateMethodByPoet(TypeElement typeElement, Map<Integer, VariableElement> variableElementMap) {
ClassName className = ClassName.bestGuess(typeElement.getQualifiedName().toString());
//方法参数名
String parameter = "_" + StringUtil.toLowerCaseFirstChar(className.simpleName());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(className, parameter);
for (int viewId : variableElementMap.keySet()) {
VariableElement element = variableElementMap.get(viewId);
//被注解的字段名
String name = element.getSimpleName().toString();
//被注解的字段的对象类型的全名称
String type = element.asType().toString();
String text = "{0}.{1}=({2})({3}.findViewById({4}));\n";
methodBuilder.addCode(MessageFormat.format(text, parameter, name, type, parameter, String.valueOf(viewId)));
}
return methodBuilder.build();
}
}
引入
1
2
3
4
5
6
7
8
9
10
11
12
public class ButterKnife {
public static void bind(Activity activity) {
Class clazz = activity.getClass();
try {
Class bindViewClass = Class.forName(clazz.getName() + "ViewBinding");
Method method = bindViewClass.getMethod("bind", clazz);
method.invoke(null, activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}

debug apt

如何debug自定义AbstractProcessor 好用

Android APT 编译期进入debug模式
选 app 或者生成代码的 module

1
2
3
4
5
6
netstat -aon|findstr "5005"
tasklist|findstr "5005"
gradlew --daemon

gradlew clean :app:assembleDebug -Dorg.gradle.debug=true --no-daemon
gradlew clean assembleDebug

AspectJ

AspectJ 就是面向切面编程在 Java 中的一种具体实现。

Join point:程序中执行代码插入的点,例如方法调用时或者方法执行时。

Android开发之AOP编程(不错)

1
2
3
4
5
6
7
8
9
10
private static boolean enabled = true;

//在连接点进行方法替换
@Around("execution(@com.allin.medrecorddossier.aop.annotation.PMUserPermission * *(..))")
public Object aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
if (enabled) {
return joinPoint.proceed();//执行原方法
}
return null;
}

双击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@Aspect  
public class AspectOnClick {

//上次点击的时间
private static Long sLastclick = 0L;

private static final Long FILTER_TIMEM = 500L;
//上次点击事件View
private WeakReference<View> weakReference;

//是否过滤点击 默认是
private boolean checkClick = true;

@Pointcut("execution(* android.view.View.OnClickListener.onClick(..))")
public void setOnClick() {
}

@Around("setOnClick()")
public void aroundSetOnClick(ProceedingJoinPoint proceedingJoinPoint ) throws Throwable {
if (System.currentTimeMillis() - sLastclick >= FILTER_TIMEM) {
doClick(proceedingJoinPoint);
} else {
//---- update content ----- 判断是否需要过滤点击
//小于指定秒数 但是不是同一个view 可以点击 或者不过滤点击
if (!checkClick || weakReference == null || weakReference.get() != proceedingJoinPoint.getArgs()[0]) {
doClick(proceedingJoinPoint);
} else {
//大于指定秒数 且是同一个view
// Log.e(TAG, "重复点击,已过滤");
}
}
}


//执行原有的 onClick 方法
private void doClick(ProceedingJoinPoint joinPoint) throws Throwable {
//判断 view 是否存在
if (joinPoint.getArgs().length == 0) {
joinPoint.proceed();
return;
}
//记录点击的view
View lastView = (View) (joinPoint).getArgs()[0];
weakReference = new WeakReference<>(lastView);
//记录点击事件
sLastclick = System.currentTimeMillis();
//执行点击事件
joinPoint.proceed();

}
}

IOC (思想)

原来由程序代码中主动获取的资源,转变由第三方获取并使原来的代码被动接收的方式,以达到解耦的效果,称为控制反转
unknown_filename.4|600

  1. 运行时注入 xUtils, eventBus, springMVC (在运行的时候获取注解、解析注解)
  2. 源码时注入 android studio 插件
  3. 编译时注入 butterknife, dagger2 (apt 就是编译时注入,生成文件,运行的时候加载这个文件调方法就可以了)
1
2
3
4
5
6
7
8
9
10
11
12
public class JettButterknife {
public static void bind(Activity activity){
String name=activity.getClass().getName()+"_ViewBinding";
try{
Class<?> aClass=Class.forName(name);
IBinder iBinder=(IBinder)aClass.newInstance();
iBinder.bind(activity);
}catch(Exception e){

}
}
}

注解、AOP、APT
http://peiniwan.github.io/2024/04/e4ef2fb1c08e.html
作者
六月的雨
发布于
2024年4月6日
许可协议