注解
声明一个注解类型
public @interface Lance{
}
元注解
在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta�annotation(元注解)。
- @Target
注解标记另一个注解,以限制可以应用注解的 Java 元素类型。目标注解指定以下元素类型之一作为其值:
ElementType.ANNOTATION_TYPE 可以应用于注解类型。
ElementType.CONSTRUCTOR 可以应用于构造函数。
ElementType.FIELD 可以应用于字段或属性。
ElementType.LOCAL_VARIABLE 可以应用于局部变量。
ElementType.METHOD 可以应用于方法级注解。
ElementType.PACKAGE 可以应用于包声明。
ElementType.PARAMETER 可以应用于方法的参数。
ElementType.TYPE 可以应用于类的任何元素。 - @Retention
注解指定标记注解的存储方式:
RetentionPolicy.SOURCE - 标记的注解仅保留在源级别中,并被编译器忽略。
RetentionPolicy.CLASS - 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。
RetentionPolicy.RUNTIME - 标记的注解由 JVM 保留,因此运行时环境可以使用它。
@Retention 三个值中 SOURCE < CLASS < RUNTIME,即CLASS包含了SOURCE,RUNTIME包含SOURCE、
CLASS。下文会介绍他们不同的应用场景。
注解应用场景
SOURCE
RetentionPolicy.SOURCE ,作用于源码级别的注解,可提供给IDE语法检查、APT等场景使用。
在类中使用 SOURCE 级别的注解,其编译之后的class中会被丢弃。
- IDE语法检查
在Android开发中, support-annotations 与 androidx.annotation) 中均有提供 @IntDef 注解,此注解的定义如下:
@Retention(SOURCE) //源码级别注解
@Target({ANNOTATION_TYPE})
public @interface IntDef {
int[] value() default {};
boolean flag() default false;
boolean open() default false;
}
Java中Enum(枚举)的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中。
比常量多5到10倍的内存占用。此注解的意义在于能够取代枚举,实现如方法入参限制。
如:我们定义方法 test ,此方法接收参数 teacher 需要在:Lance、Alvin中选择一个。如果使用枚举能够实现为:
public enum Teacher{
LANCE,ALVIN
}
public void test(Teacher teacher) {
}
而现在为了进行内存优化,我们现在不再使用枚举,则方法定义为:
public static final int LANCE = 1;
public static final int ALVIN = 2;
public void test(int teacher) {
}
然而此时,调用 test 方法由于采用基本数据类型int,将无法进行类型限定。此时使用@IntDef增加自定义注解:
public static final int LANCE = 1;
public static final int ALVIN = 2;
@IntDef(value = {MAN, WOMEN}) //限定为LANCE,ALVIN @Target(ElementType.PARAMETER) //作用于参数的注解 @Retention(RetentionPolicy.SOURCE) //源码级别注解
public @interface Teacher {
}
public void test(@Teacher int teacher) {
}
此时,我们再去调用 test 方法,如果传递的参数不是 LANCE 或者 ALVIN 则会显示 Inspection 警告(编译不会报错)。
以上注解均为 SOURCE 级别,本身IDEA/AS 就是由Java开发的,工具实现了对Java语法的检查,借助注解能对被注解的特定语法进行额外检查。
- APT注解处理器
APT全称为:"Anotation Processor Tools",意为注解处理器。顾名思义,其用于处理注解。编写好的Java源文件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac调起,并将注解信息传递给注解处理器进行处理。
注解处理器是对注解应用最为广泛的场景。在Glide、EventBus3、Butterknifer、Tinker、ARouter等等常用框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是 SOURCE 级别,更多的是 CLASS 级别,别忘了:CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。
关于注解处理器的实现,在后续课程中会有相当多的介绍。此处先不进行详细介绍。
CLASS
定义为 CLASS 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。此时完全符合此种注解的应用场景为字节码操作。如:AspectJ、热修复Roubust中应用此场景。
所谓字节码操作即为,直接修改字节码Class文件以达到修改代码执行逻辑的目的。在程序中有多处需要进行是否登录的判断。
image.png
如果我们使用普通的编程方式,需要在代码中进行 if-else 的判断,也许存在十个判断点,则需要在每个判断点加入此项判断。此时,我们可以借助AOP(面向切面)编程思想,将程序中所有功能点划分为: 需要登录 与 无需登录两种类型,即两个切面。对于切面的区分即可采用注解。
//Java源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Login {
}
@Login
public void jumpA(){
startActivity(new Intent(this,AActivity.class));
}
public void jumpB(){
startActivity(new Intent(this,BActivity.class));
}
在上诉代码中, jumpA 方法需要具备登录身份。而 Login 注解的定义被设置为 CLASS 。因此我们能够在该类所编译的字节码中获得到方法注解 Login 。在操作字节码时,就能够根据方法是否具备该注解来修改class中该方法的内容加入 if-else 的代码段:
//Class字节码
@Login public void jumpA() {
if (this.isLogin) {
this.startActivity(new Intent(this, LoginActivity.class));
} else {
this.startActivity(new Intent(this, AActivity.class));
}
}
public void jumpB() {
startActivity(new Intent(this,BActivity.class));
}
注解能够设置类型元素(参数),结合参数能实现更为丰富的场景,如:运行期权限判定等。
RUNTIME
注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。
反射
一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的,并且能够获得此类的引用。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。
反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用。
反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。是Java被视为动态语言的关键。
Java反射机制主要提供了以下功能:
- 在运行时构造任意一个类的对象
- 在运行时获取或者修改任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法(属性)
Gson反序列化
static class Response<T> {
T data;
int code;
String message;
@Override
public String toString() {
return "Response{" + "data=" + data + ", code=" + code + ", message='" + message + '\'' + '}';
}
public Response(T data, int code, String message) {
this.data = data;
this.code = code;
this.message = message;
}
}
static class Data {
String result;
public Data(String result) {
this.result = result;
}
@Override
public String toString() {
return "Data{" + "result=" + result + '}';
}
}
public static void main(String[] args) {
Response<Data> dataResponse = new Response(new Data("数据"), 1, "成功");
Gson gson = new Gson(); String json = gson.toJson(dataResponse);
System.out.println(json);
//为什么TypeToken要定义为抽象类?
Response<Data> resp = gson.fromJson(json, new TypeToken<Response<Data>>() { }.getType());
System.out.println(resp.data.result);
}
在进行GSON反序列化时,存在泛型时,可以借助 TypeToken 获取Type以完成泛型的反序列化。但是为什么TypeToken 要被定义为抽象类呢?
因为只有定义为抽象类或者接口,这样在使用时,需要创建对应的实现类,此时确定泛型类型,编译才能够将泛型signature信息记录到Class元数据中。
控件绑定
/**
*
*/
public class InjectViewUtils {
public static void injectView(Activity activity) {
if (null == activity) return;
Class<? extends Activity> activityClass = activity.getClass();
Field[] declaredFields = activityClass.getDeclaredFields();
for (Field field : declaredFields) {//获取Activity类里面声明的所有成员变量
if (field.isAnnotationPresent(InjectView.class)) {//找出标注了@InjectView的成员变量
//解析InjectView 获取button id
InjectView injectView = field.getAnnotation(InjectView.class);
int value = injectView.value();//获取变量的值,也就是控件的id
try {
//找到findViewById方法
Method findViewById = activityClass.getMethod("findViewById", int.class);
findViewById.setAccessible(true);
Object view = findViewById.invoke(activity, value);
field.set(activity, view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static void injectAutowired(Activity activity) {
Class<? extends Activity> cls = activity.getClass();
//获得数据
Intent intent = activity.getIntent();
Bundle extras = intent.getExtras();
if (extras == null) {
return;
}
//获得此类所有的成员
Field[] declaredFields = cls.getDeclaredFields();
for (Field field : declaredFields) {
if (field.isAnnotationPresent(Autowired.class)) {
Autowired autowired = field.getAnnotation(Autowired.class);
//获得key
String key = TextUtils.isEmpty(autowired.value()) ? field.getName() : autowired.value();
if (extras.containsKey(key)) {
Object obj = extras.get(key);
// todo Parcelable数组类型不能直接设置,其他的都可以.
//获得数组单个元素类型
Class<?> componentType = field.getType().getComponentType();
//当前属性是数组并且是 Parcelable(子类)数组
if (field.getType().isArray() &&
Parcelable.class.isAssignableFrom(componentType)) {
Object[] objs = (Object[]) obj;
//创建对应类型的数组并由objs拷贝
Object[] objects = Arrays.copyOf(objs, objs.length, (Class<? extends Object[]>) field.getType());
obj = objects;
}
field.setAccessible(true);
try {
field.set(activity, obj);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
网友评论