@TryLoveCatch
2023-03-07T16:18:59.000000Z
字数 12350
阅读 802
Java知识体系
注解是一种元数据(描述数据的数据),注解本质是一个继承了Annotation 的特殊接口。
描述作用,不会直接生效,需要在编译前/运行时获取注解信息
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
public interface Override extends Annotation{
}
元注解是修饰其他注解的注解。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
//属性叫 value ,在使用时可以直接传参数即可,不必显式的指明键值对,是一种快捷方法
int value();
}
我们可以使用 default xxx 为注解的某个属性指定默认值,这样即使不指定某个属性,编译器也不会报错。这通常可以节约很多时间,比如这样:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Author {
String name() default "shixinzhang";
String date();
}
注解处理器分为两种:
运行时注解需要使用 注解 + 反射 ,非常简单。
我们先自定义一个 ContentView 注解,表示当前布局对应的 layout 文件:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
//属性叫 value ,在使用时可以直接传参数即可,不必显式的指明键值对,是一种快捷方法
int value() ;
}
然后用它修饰一个 Activity:
@ContentView(R.layout.activity_annotation)
public class AnnotationTestActivity extends BaseActivity {
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); //调用父类的 onCreate
}
}
在 BaseActivity 中反射获取当前类使用的注解,拿到注解的值,就可以直接设置布局了:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
annotationProcess();
}
//读取注解,进行处理
private void annotationProcess() {
Class c = this.getClass();
//遍历所有子类
for (; c != Context.class; c = c.getSuperclass()) {
//找到使用 ContentView 注解的类
ContentView annotation = (ContentView) c.getAnnotation(ContentView.class);
if (annotation != null) {
try { //有可能出错的地方都要 try-catch
//获取 注解中的属性值,为 Activity 设置布局
this.setContentView(annotation.value());
} catch (RuntimeException e) {
e.printStackTrace();
}
return;
}
}
}
这样就简单实现了运行时根据注解动态设置布局的功能。
使用编译时注解简单实现类似 ButterKnife 的效果
处理编译时注解需要使用 APT。
APT 即 Annotation Processing Tool,注解处理工具,它可以在编译时检测源代码文件,找到符合条件的注解修饰的内容,然后进行相应的处理。
我们在使用 ButterKnife,gradle 依赖中的 apt 就是指定在编译时调用它们的注解处理器:
compile "com.jakewharton:butterknife:$rootProject.butterknifeVersion"
apt "com.jakewharton:butterknife-compiler:$rootProject.butterknifeVersion"
编译时注解的使用一般分为三步:
这里,我们写一个类似 ButterKnife 使用注解实现 findViewById 的 demo。
大概思路就是这样子:
注解处理器所在的 module 必须是 Java Library,因为要用到特有的 javax;
注解处理器需要依赖 注解 module,所以注解所在的 module 也要是 Java Library;
运行时绑定的类要操作 Activity 或者 View,所以需要为 Android Library。
New 一个 Module,选择为 Java library,我们起名为 ioc-annotation
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
编译时注解的 Retention 为 RetentionPolicy.CLASS,即只在编译时保留。
修饰目标为 ElementType.FIELD,即成员变量。
我们需要在 Activity 中调用一个绑定的方法,它的作用调用生成类方法,来完成findviewbyid的过程。类似 ButterKnife:
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_annotation);
ViewBinder.bind(this);
}
New 一个 Module,选择为 Android library,我们起名为 ioc,创建 ViewBinder
**
* Description:
* <br> 从生成类中为当前 Activity/View 中的 View findViewById
*/
public class ViewBinder {
private static final String SUFFIX = "$$ViewInjector";
//Activity 中调用的方法
public static void bind(Activity activity) {
bind(activity, activity);
}
/**
* 1.寻找对应的代理类
* 2.调用接口提供的绑定方法
*
* @param host
* @param root
*/
@SuppressWarnings("unchecked")
private static void bind(final Object host, final Object root) {
if (host == null || root == null) {
return;
}
Class<?> aClass = host.getClass();
String proxyClassFullName = aClass.getName() + SUFFIX; //拼接生成类的名称
try {
Class<?> proxyClass = Class.forName(proxyClassFullName);
ViewInjector viewInjector = (ViewInjector) proxyClass.newInstance();
if (viewInjector != null) {
viewInjector.inject(host, root);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
MyActivity$$ViewInjector,这个就是我们约定好的生产的类名。
ViewInjector,是一个接口,主要用来反射调用。
public interface ViewInjector<T> {
void inject(T t, Object source);
}
注解处理器的作用是读取注解、生成代码,按照我们约定好的类名来生成类,并且包含findViewById的代码。
具体的参考链接,这里就不赘述了。
public class AnnotationTestActivity extends BaseActivity {
@BindView(R.id.tv_content)
public TextView mTextView;
@BindView(R.id.tv_bottom_content)
public TextView mBottomTextView;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_annotation);
ViewBinder.bind(this);
}
//...
}
点击 Build -> Rebuild Project,然后在 app -> build -> generated -> source -> apt -> flavor名字 -> 使用注解的包名下,看到生成类。
自定义一个运行时注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HelloAnnotation {
String say() default "Hi";
}
然后在Main函数中解析注解
@HelloAnnotation(say = "Do it!")
public class TestMain {
public static void main(String[] args) {
HelloAnnotation annotation = TestMain.class.getAnnotation(HelloAnnotation.class);//获取TestMain类上的注解对象
System.out.println(annotation.say());//调用注解对象的say方法,并打印到控制台
}
}
运行程序,输出结果如下:
Do it!
我们来看一下HelloAnnotation的字节码
$ javap -verbose HelloAnnotation
Warning: Binary file HelloAnnotation contains com.kevin.java.annotation.runtimeAnnotation.HelloAnnotation
Classfile /home/kevin/Workspace/IdeaProjects/JavaLearn/out/production/JavaLearn/com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation.class
Last modified Aug 6, 2016; size 496 bytes
MD5 checksum a6c87f863669f6ab9050ffa310160ea5
Compiled from "HelloAnnotation.java"
public interface com.kevin.java.annotation.runtimeAnnotation.HelloAnnotation extends java.lang.annotation.Annotation
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
#1 = Class #18 // com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation
#2 = Class #19 // java/lang/Object
#3 = Class #20 // java/lang/annotation/Annotation
#4 = Utf8 say
#5 = Utf8 ()Ljava/lang/String;
#6 = Utf8 AnnotationDefault
#7 = Utf8 Hi
#8 = Utf8 SourceFile
#9 = Utf8 HelloAnnotation.java
#10 = Utf8 RuntimeVisibleAnnotations
#11 = Utf8 Ljava/lang/annotation/Target;
#12 = Utf8 value
#13 = Utf8 Ljava/lang/annotation/ElementType;
#14 = Utf8 TYPE
#15 = Utf8 Ljava/lang/annotation/Retention;
#16 = Utf8 Ljava/lang/annotation/RetentionPolicy;
#17 = Utf8 RUNTIME
#18 = Utf8 com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation
#19 = Utf8 java/lang/Object
#20 = Utf8 java/lang/annotation/Annotation
{
public abstract java.lang.String say();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: s#7}
SourceFile: "HelloAnnotation.java"
RuntimeVisibleAnnotations:
0: #11(#12=[e#13.#14])
1: #15(#12=e#16.#17)
看第7行,很明显,HelloAnnotation就是继承了Annotation的接口。
再看第10行,flag字段中,我们可以看到,有个ACC_ANNOTATION标记,说明是一个注解,所以
注解本质是一个继承了Annotation的特殊接口。
看一下Annotation接口声明
package java.lang.annotation;
public interface Annotation {
boolean equals(Object var1);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
我们运行TestMain,并打断点,看一下HelloAnnotation具体是什么类的对象:
可以看到HelloAnnotation注解的实例是jvm生成的动态代理类的对象。
我们来看一下这个生成的代理类:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import com.kevin.java.annotation.runtimeAnnotation.HelloAnnotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy1 extends Proxy implements HelloAnnotation {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m3;
private static Method m0;
public $Proxy1(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final Class annotationType() throws {
try {
return (Class)super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String say() throws {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m4 = Class.forName("com.kevin.java.annotation.runtimeAnnotation.HelloAnnotation").getMethod("annotationType", new Class[0]);
m3 = Class.forName("com.kevin.java.annotation.runtimeAnnotation.HelloAnnotation").getMethod("say", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
从第14行我们可以看到,我们自定义的注解HelloAnnotation是一个接口,而$Proxy1这个Java生成的动态代理类就是它的实现类。
- 现在我们知道了HelloAnnotation注解(接口)是一个继承了Annotation接口的特殊接口。
- 而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1,该类就是HelloAnnotation注解(接口)的具体实现类。
既然是Proxy,那么就应该有一个InvocationHandler来处理代理,通过断点,我们可以知道这个对应的就是AnnotationInvocationHandler:
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;
....
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
this.type = type;
this.memberValues = memberValues;
}
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
assert paramTypes.length == 0;
if (member.equals("toString"))
return toStringImpl();
if (member.equals("hashCode"))
return hashCodeImpl();
if (member.equals("annotationType"))
return type;
// Handle annotation member accessors
Object result = memberValues.get(member);
if (result == null)
throw new IncompleteAnnotationException(type, member);
if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();
if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);
return result;
}
...
...
}
我们一眼就可以看到一个有意思的名字: memberValues,这是一个Map,而断点中可以看到这是一个 LinkedHashMap,key为注解的属性名称,value即为注解的属性值。
- 注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。
- 而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1。
- 通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。
- 该方法会从memberValues这个Map中索引出对应的值。
- 而memberValues的来源是Java常量池。
简单来说,注解就是接口+Map,然后通过动态代理将他们组合起来。
apt不再神秘
Android APT 实例讲解
Android开源系列-组件化框架Arouter-(三)APT技术详解
KotlinPoet简介
apt怎么参与到javac的编译过程的?
apt与javac约定在META-INF/services/javax.annotation.processing.Processor文件中注册apt插件。
这个逻辑,让我感觉APT是基于SPI实现的
apt不能修改已有代码的执行流程,如果要实现类似“日志打点”等在原代码的基础上插入一些功能,一般需要通过aspectj来实现。这类功能是在编译后期(javac之后)修改字节码文件来实现。
implementation "com.squareup:javapoet:1.11.1"
compile 'com.squareup:kotlinpoet:1.12.0'
Java 进阶巩固:什么是注解以及运行时注解的使用
使用编译时注解简单实现类似 ButterKnife 的效果
Android 如何编写基于编译时注解的项目