赞
踩
Annotation(注解)是一种标记,使类或接口附加额外信息,帮助编译器和 JVM 完成一些特定功能。
Annotation(注解)也被称为元数据(Metadata)是JDK1.5及以后版本引入的,用于修饰包、类、接口、字段、方法参数、局部变量等。
常见的注解如:@Override、@Deprecated和@SuppressWarnings
步骤:定义注解 -> 获取注解 -> 创建注解实例 -> 解析注解 ->使用。
定义如下:
- public @interface Persions{
- Person[] value();
- }
-
- @Repeatable(Persons.class)
- public @interface Person{
- String role default "";
- }
-
- 一个人他既是程序员又是产品经理,同时他还是个画家
-
- @Person(role="artist")
- @Person(role="coder")
- @Person(role="PM")
- public class SuperMan{
-
- }
注解@interface 是一个实现了Annotation接口的 接口, 然后在调用getDeclaredAnnotations()方法的时候,返回一个代理$Proxy对象,这个是使用jdk动态代理创建,使用Proxy的newProxyInstance方法时候,传入接口 和InvocationHandler的一个实例(也就是 AnotationInvocationHandler ) ,最后返回一个代理实例。
期间,在创建代理对象之前,解析注解时候 从该注解类的常量池中取出注解的信息,包括之前写到注解中的参数,然后将这些信息在创建 AnnotationInvocationHandler时候 ,传入进去 作为构造函数的参数,当调用该代理实例的获取值的方法时,就会调用执行AnotationInvocationHandler里面的逻辑,将之前存入的注解信息 取出来
获取注解
- // 1. 获取当前class
- Class<?> clazz = context.getClass();
- // 2. 根据class获取class上面的InjectLayout注解
- InjectLayout annotation = clazz.getAnnotation(InjectLayout.class);
-
-
- 步骤:
-
- 1、首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解
-
- public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
-
- 2、然后通过 getAnnotation() 或者是 getAnnotations() 方法来获取 Annotation 对象
-
- public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {} 返回指定类型的注解
- public Annotation[] getAnnotations() {} 返回注解到这个元素上的所有注解
-
- 3、 如果获取到的 Annotation 如果不为 null,则就可以调用它们的属性方法了
注解是将参数信息存储到了class文件的常量池里面,在创建实例的时候,会通过getConstantPool()获取出来,是一个byte[]流,需要进行转换。
如组件化框架、view注解框架、面向编译器/apt使用、自定义注解+拦截器或者AOP,使用自定义注解设计框架等。
1、生成文档,通过代码里标识的元数据生成javadoc文档。
2、编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。(源码时注解)
3、编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。 (编译时注解)
4、运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。(运行时注解)
java注解的原理是利用反射机制来实现的。当运行java程序时,java虚拟机会加载java类,并通过反射机制来获取类中的注解信息。通过反射机制可以获取某个类上、属性上或者方法上的注解信息。从而通过注解的信息来完成相应的操作。
如下图:反射相关的类Class, Method, Field都实现了AnnotationElement接口,因此,只要我们通过反射拿到Class, Method, Field类,就能够通过getAnnotation(Class)拿到我们想要的注解并取值
下面是两个相关的概念:
1. 注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation),注解处理器是运行它自己的虚拟机JVM中。
2. AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的方法来访问Annotation信息。
元注解就是解释注解的注解。(负责对其它注解进行说明的注解)
在JDK 1.5中提供了4个标准的元注解:
@Target,@Retention,@Documented,@Inherited,
在JDK 1.8中提供了两个元注解 @Repeatable 和 @Native 。
指定注解运用到的地方。
Target注解用来说明那些被它所注解的注解类可修饰的对象范围。
类比作标签,原本标签是想贴到什么地方就贴到什么地方,但是有了@Target的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等。
TYPE, // 类、接口、枚举类
FIELD, // 成员变量(包括:枚举常量)
METHOD, // 成员方法
PARAMETER, // 方法参数
CONSTRUCTOR, // 构造方法
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE, // 注解类
PACKAGE, // 可用于修饰:包
TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
相当于一个时间戳。
描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时) 。
类比作标签,原本标签是想贴到什么地方就贴到什么地方,想贴到什么时候就贴到什么时候,但是有了@Retention的存在,就相当于加了一个时间戳,时间戳指明了标签张贴的时间周期。
取值如下:
RetentionPolicy.SOURCE:注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视,这类注解不会被编译进入.class文件
RetentionPolicy.CLASS:注解只被保留到编译进行的时候,它并不会被加载到JVM中,这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中
RetentionPolicy.RUNTIME:注解可以保留到程序运行的时候,会被加载进入到JVM中,所以程序运行时可以获取到。
只有注解被定义为RUNTIME后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息
被它修饰的Annotation将具有继承性。
如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。
Inherited是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被@Inherited注解过的注解进行注解的话(注解了B注解,B在注解其他),那么它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解
重复注解
其核心思想是java的ioc(inversion of control),也叫di(dependency injection,依赖注入),是一种面向对象编程中的设计模式。
来写一下布局文件的注入,比如我们不想写烦人的setContentView方法,直接用个注解来搞定,
首先,开始定义一个布局注解类:
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface InjectLayout {
- int value();
- }
-
- 创建一个类来获取布局文件并设置contentview:
- 获取到注解类中的值,然后通过反射执行activity中的setContentView方法
- class InjectUtils {
- static void injectLayout(Context context) {
- // 1. 获取当前class
- Class<?> clazz = context.getClass();
- // 2. 根据class获取class上面的InjectLayout注解
- InjectLayout annotation = clazz.getAnnotation(InjectLayout.class);
- // 判空
- if (annotation == null) return;
-
- // 3. 获取注解中的值,这里就是布局文件的id
- int layoutId = annotation.value();
- try {
- // 4. 获取activity中的setContentView方法
- Method method = clazz.getMethod("setContentView", int.class);
- // 5. 执行setContentView方法,传入layoutId参数
- method.invoke(context, layoutId);
- } catch (Exception e) {
- }
- }
- }
view的注入逻辑
- static void injectView(Context context) {
- // 1. 获取当前class
- Class<?> clazz = context.getClass();
-
- // 2. 获取activity中所有的成员变量
- Field[] declaredFields = clazz.getDeclaredFields();
- // 3. 开始遍历
- for (Field field : declaredFields) {
- field.setAccessible(true);
- // 4. 获取字段上面的InjectView注解
- InjectView annotation = field.getAnnotation(InjectView.class);
- // 5. 如果字段上面没有注解,就不用处理了
- if (annotation == null) {
- return;
- }
- int viewId = annotation.value();
- try {
- // 6. 获取 findViewById 方法
- Method findViewMethod = clazz.getMethod("findViewById", int.class);
- // 7. 执行方法,获取View
- View view = (View) findViewMethod.invoke(context, viewId);
- // 8. 把view赋值给该字段
- field.set(context, view);
- } catch (Exception e) {
- }
- }
- }
-
-
- 事件的注入思路就是通过事件类型获取事件的类型和方法名,然后通过代理取到事件的方法,当执行事件的时候自动执行我们在activity中定义的事件方法。
使用:
- @InjectLayout(R.layout.activity_java)
- public class JavaActivity extends AppCompatActivity {
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
- }
-
- 有个布局文件
- xxxxx
1、引入AutoService,用于自动生成SPI清单文件
2、自定义注解处理器,自动将activity注入到Map集合中的,以供后续使用
3、生成path文件
由于篇幅所限,请移步apt文章查看
每个模块都相当于一个组(group),每个组里面由于有多个Activity, 所以每个Activity又维护了一个路径(path),当我们要跳转的时候,通过group找到对应的模块,再通过path找到具体的class。
1、定义两个注解
- @Target(ElementType.TYPE) // 该注解作用在类之上
- @Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作,注解会在class文件中存在
- public @interface IRouter {
- // 详细路由路径(必填),如:"/app/MainActivity"
- String path();
- // 路由组名(选填,如果开发者不填写,可以从path中截取出来)
- String group() default "";
- }
-
- @Target(ElementType.FIELD) // 该注解作用在属性之上
- @Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作,注解会在class文件中存在
- public @interface Parameter {
- // 不填写name的注解值表示该属性名就是key,填写了就用注解值作为key
- // 从getIntent()方法中获取传递参数值
- String name() default "";
- }
2、创建注解编译处理器模块 compiler
在注解处理器模块的gradle中引入AutoService,用于帮我们生成MATE-INF.services下的文件,需要这个文件系统才能帮我们识别是一个注解处理器.
3、编译模块自定义编译时注解 AbstractProcessor
- 这个类主要用于解析注解并生成文件
-
- class IRouterProcessor : AbstractProcessor() {
- override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?): Boolean {
-
- // 获取所有的被注解的节点
- Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(IRouter.class);
-
- // 获取注解的path变量
- IRouter iRouter = element.getAnnotation(IRouter.class);
- val path = iRouter.path
-
- 生成path
- 用javapoet生成对应的文件
-
- return true
- }
- }
- public abstract boolean process(Set<? extends TypeElement> var1, RoundEnvironment var2);
-
- 这个就是所有注解的元素的集合,它的泛型是TypeElement的下限类型
我们注解的每一个元素,其实就是被包装成了一个个的Element放进了Set集合中
Element有以下几个实现类,代表了不同的元素
- PackageElement 表示一个包程序元素。提供对有关包及其成员的信息的访问
- ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序(静态或实例)
- TypeElement 表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问
- VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
Element节点中的API
- getEnclosedElements() 返回该元素直接包含的子元素
- getEnclosingElement() 返回包含该element的父element,与上一个方法相反
- getKind() 返回element的类型,判断是哪种element
- getModifiers() 获取修饰关键字,入public static final等关键字
- getSimpleName() 获取名字,不带包名
- getQualifiedName() 获取全名,如果是类的话,包含完整的包名路径
- getParameters() 获取方法的参数元素,每个元素是一个VariableElement
- getReturnType() 获取方法元素的返回值
- getConstantValue() 如果属性变量被final修饰,则可以使用该方法获取它的值
javapoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件 这个框架功能非常实用.
分别用于存储每个module及module下的路径
使用:
- @IRouter(path = "/order/ooo”)
- public class DerActivity extends AppCompatActivity {
-
- @IRouter(path = "/user/test”)
- public class UserActivity extends AppCompatActivity {
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。