今天开始架构师之路!分为6节课,以手写retofit ,Butterknife,Arount,Dagger2,hilit,ASM ,AOP为主
说说你对注解的了解,是怎么解析的
编译时注解与运行时注解,为什么retrofit要使用运行时注解?什么时候用运行时注解?
编译期注解处理的是字节码还是java文件
它们的生命周期不一样,有些接口不用在编译时处理。他们没有用到。 所以运行时更适合
Retrofit:运行时注解,需要用的时候才用到
重写:编译的时候用到的。
运行时注解与编译时注解的区别是什么呢?
a)保留阶段不同。运行时注解保留到运行时,可在运行时访问。而编译时注解保留到编译时,运行时无法访问。
b)原理不同。运行时注解是Java反射机制,而编译时注解通过APT、AbstractProcessor。
c)性能不同。运行时注解由于使用Java反射,因此对性能上有影响。编译时注解对性能没影响。这也是为什么ButterKnife从运行时切换到了编译时的原因。
d)产物不同。运行时注解只需自定义注解处理器即可,不会产生其他文件。而编译时注解通常会产生新的Java源文件。
---------------------
Annotation(注解)就是Java提供了一种源程序中的元素关联任何信息或者任何元数据(metadata)的途径和方法。
Annotation是被动的元数据,永远不会有主动行为
既然是被动数据,对于那些已经存在的注解,比如Override,我们只能看看而已,并不知道它具体的工作机制是什么;所以想要理解注解,就直接从自定义注解开始。
元注解
可以看到自定义注解里也会有注解存在,给自定义注解使用的注解就是元注解。
转载:http://blog.csdn.net/lmj623565791/article/details/39269193,本文出自:【张鸿洋的博客】
注解四大组成,二大必须组成
@Target(必须)表示该注解可以用于什么地方,可能的ElementType参数有:
CONSTRUCTOR:构造器的声明
FIELD:域声明(包括enum实例)
LOCAL_VARIABLE:局部变量声明
METHOD:方法声明
PACKAGE:包声明
PARAMETER:参数声明
TYPE:类、接口(包括注解类型)或enum声明
@Retention(常用)表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:
SOURCE:注解将被编译器丢弃
CLASS:注解在class文件中可用,但会被VM丢弃
RUNTIME:VM将在运行期间保留注解,因此可以通过反射机制读取注解的信息。
@Document将注解包含在Javadoc中
@Inherited允许子类继承父类中的注解
@Target:
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值(ElementType)有:和反射对应
TYPE, //适用class,interface,enum
FIELD, //适用field
METHOD,//适用method
PARAMETER, //适用method之上的parameter
CONSTRUCTOR, //适用constructor
LOCAL_VARIABLE, // 适用区域变量
ANNOTATION_TYPE, //适用annotation类型
PACKAGE //适用package
@Retention:
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
@Rentention Rentention用来标记自定义注解的有效范围,他的取值有以下三种:
取值(RetentionPoicy)有:
RetentionPolicy.SOURCE: 只在源代码中保留 一般都是用来增加代码的理解性或者帮助代码检查之类的,比如我们的Override;
RetentionPolicy.CLASS: 默认的选择,能把注解保留到编译后的字节码class文件中,仅仅到字节码文件中,运行时是无法得到的;
RetentionPolicy.RUNTIME: ,注解不仅 能保留到class字节码文件中,还能在运行通过反射获取到,这也是我们最常用的。
如果不写的话,会怎样??
@Documented:
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
注解的继承:
注解是如何继承的!
@Inherited:
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
自定义注解:
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
定义注解格式:
public @interface 注解名 {定义体}
注解参数的可支持数据类型:
1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2.String类型
3.Class类型
4.enum类型
5.Annotation类型
6.以上所有类型的数组
Annotation类型里面的参数该怎么设定:
第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;
第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;
1
2
3
4
5
6
7
8
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
@Inherited
public @interface Bind {
int value() default 1;
boolean canBeNull() default false;
}
这就是自定义注解的形式,我们用@interface 表明这是一个注解,Annotation只有成员变量,没有方法。Annotation的成员变量在Annotation定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。比如上面的value和canBeNull。
如果我们是自定义注解,则通过前面分析,我们自定义注解如果只存着源码中或者字节码文件中就无法发挥作用,而在运行期间能获取到注解才能实现我们目的,所以自定义注解中肯定是使用 @Retention(RetentionPolicy.RUNTIME)
使用反射获取注解信息
前面已经说了,Annotation是被动的元数据,永远不会有主动行为,所以我们需要通过使用反射,才能让我们的注解产生意义。
反射&注解的使用:
举例:获取retrofit上面的注解信息 demo:
第一步:定义GET注解
import static java.lang.annotation.ElementType.METHOD;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author: pengcaihua
* @date: 2021/12/22
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(METHOD)
@Documented
public @interface GET {
Stringvalue()default "";
}
第二步:使用运行时注解
public interface ApiService {
@GET("app/payOrder")
ObservablecreateMutilOrder(@QueryMap Map map);
@POST("app/queryOrder")
// 请求地址可以放在这里
ObservablepostRequest1();//Json代码数据返回的实体类
}
第三步:反射解析得到运行时注解
* 目的:获取得到方法的注解
* 1.得到当前类
* 2.知道当前方法
* 3.获取注解的内容
* @return
*/
public StringgetMethodAnotation() {
//获取类字节码
Class cls = ApiService.class;
try {
//得到方法,后面是方法类型的class
Method method = cls.getMethod("createMutilOrder", Map.class);
//通过方法得到方法上面的注解
GET get = method.getAnnotation(GET.class);
//通过注解,调用注解的接口得到值
String value = get.value();
Log.d("peng", "value:" + value);
}catch (Exception e) {
e.printStackTrace();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
泛型是怎么解析的,比如在retrofit中的泛型是怎么解析的
运行的时候,通过注解+反射
编译注解:注解+注解处理器
有类注解,方法注解,变量注解。
就需要先获取类,方法和变量,这个时候就必须通过反射!
编译期注解跟运行时注解
运行期注解(RunTime)利用反射去获取信息还是比较损耗性能的,对应@Retention(RetentionPolicy.RUNTIME)。
编译期(Compile time)注解,以及处理编译期注解的手段APT和Javapoet,对应@Retention(RetentionPolicy.CLASS)。 其中apt+javaPoet目前也是应用比较广泛,在一些大的开源库,如EventBus3.0+,页面路由 ARout、Dagger、Retrofit等均有使用的身影,注解不仅仅是通过反射一种方式来使用,也可以使用APT在编译期处理
获取注解对象
因为动态的生成了一个注解的对象,所以,只要“拿到”这个对象,就可以调用这个对象(注解)的方法来获取值。(例如上面调用value()方法就可以获得值)
获取注解对象,需要通过反射。只要拿到注解修饰的部分的反射对象,就可以拿到注解对象。
到这里,你应该已经明白了如何使用反射获取注解的信息,但你一定会困惑这么做有什么用呢?”动态“,”动态“,”动态“
这就是使用注解和反射最大的意义,我们可以动态的访问对象。
注解解析:相应的方法:
/**
* 获取指定类型的注解
*/
public A getAnnotation(Class annotationType);
/**
* 获取所有注解,如果有的话
*/
public Annotation[] getAnnotations();
/**
* 获取所有注解,忽略继承的注解
*/
public Annotation[] getDeclaredAnnotations();
/**
* 指定注解是否存在该元素上,如果有则返回true,否则false
*/
public boolean isAnnotationPresent(Class extends Annotation> annotationType);
/**
* 获取Method中参数的所有注解
*/
public Annotation[][] getParameterAnnotations();
几个常用的方法:
field.isAnnotationPresent:这个类是否有主注解
BindPort port = field.getAnnotation(BindPort.class); 通过变量得到类
首先遍历循环所有的属性,如果当前属性被指定的注解所修饰,那么就将当前属性的值修改为注解中成员变量的值。
上面的代码中,找到被BindPort修饰的属性,然后将BindPort中value的值赋给该属性。
这里setAccessible(true)的使用时因为,我们在声明port变量时,其类型为private,为了确保可以访问这个变量,防止程序出现异常。
方法使用注解
上面对于类属性(成员变量)设定注解,可能还不能让你感受到注解&反射的优势,我们再来看一下类的方法使用注解会怎样。
我们还是先定义一个注解
1
2
3
4
5
\
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
参考:自定义注解实现工厂模式
https://blog.csdn.net/wuyuxing24/article/details/81139846
参考博客:非常不错
https://www.jianshu.com/p/d4978bbce12a