【Java基础】学java注解,看这一篇文章就够了

简介: Annotation(注解)是一种标记,使类或接口附加额外信息,帮助编译器和 JVM 完成一些特定功能。Annotation(注解)也被称为元数据(Metadata)是JDK1.5及以后版本引入的,用于修饰包、类、接口、字段、方法参数、局部变量等。常见的注解如:@Override、@Deprecated和@SuppressWarnings

一、注解的概念
Annotation(注解)是一种标记,使类或接口附加额外信息,帮助编译器和 JVM 完成一些特定功能。

Annotation(注解)也被称为元数据(Metadata)是JDK1.5及以后版本引入的,用于修饰包、类、接口、字段、方法参数、局部变量等。

常见的注解如:@Override、@Deprecated和@SuppressWarnings

二、注解使用步骤及场景
2.1 使用步骤
步骤:定义注解 -> 获取注解 -> 创建注解实例 -> 解析注解 ->使用。

定义如下:

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[]流,需要进行转换。

2.2 常见场景
如组件化框架、view注解框架、面向编译器/apt使用、自定义注解+拦截器或者AOP,使用自定义注解设计框架等。

2.3 注解的作用:

三、注解原理及分类
3.1 原理
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信息。

3.2 元注解:
元注解就是解释注解的注解。(负责对其它注解进行说明的注解)
在JDK 1.5中提供了4个标准的元注解:
@Target,@Retention,@Documented,@Inherited,
在JDK 1.8中提供了两个元注解 @Repeatable 和 @Native 。

@Target
指定注解运用到的地方。

Target注解用来说明那些被它所注解的注解类可修饰的对象范围。

类比作标签,原本标签是想贴到什么地方就贴到什么地方,但是有了@Target的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等。

@Retention
相当于一个时间戳。

描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时) 。

类比作标签,原本标签是想贴到什么地方就贴到什么地方,想贴到什么时候就贴到什么时候,但是有了@Retention的存在,就相当于加了一个时间戳,时间戳指明了标签张贴的时间周期。

取值如下:

只有注解被定义为RUNTIME后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。

@Documented
在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息

@Inherited
被它修饰的Annotation将具有继承性。

如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。

Inherited是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被@Inherited注解过的注解进行注解的话(注解了B注解,B在注解其他),那么它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解

@Repeatable (Java8)
重复注解

四、自定义注解例子
4.1 手写ButterKnife框架(使用运行时注解的方式)
Demo 下载 :

其核心思想是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
4.2 再来一个编译时注解的例子,以组件化为例:
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?, 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源文件 这个框架功能非常实用.

4、定义路由管理类,路由管理器,辅助完成交互通信
分别用于存储每个module及module下的路径

使用:

@IRouter(path = "/order/ooo”)
public class DerActivity extends AppCompatActivity {

@IRouter(path = "/user/test”)
public class UserActivity extends AppCompatActivity {

相关文章
|
30天前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第17天】本文详细介绍了Java编程中Map的使用,涵盖Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的并发处理和性能优化技巧,适合初学者和进阶者学习。
46 3
|
30天前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
87 43
Java学习十六—掌握注解:让编程更简单
|
15天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
24天前
|
Java 开发者 Spring
[Java]自定义注解
本文介绍了Java中的四个元注解(@Target、@Retention、@Documented、@Inherited)及其使用方法,并详细讲解了自定义注解的定义和使用细节。文章还提到了Spring框架中的@AliasFor注解,通过示例帮助读者更好地理解和应用这些注解。文中强调了注解的生命周期、继承性和文档化特性,适合初学者和进阶开发者参考。
45 14
|
24天前
|
前端开发 Java
[Java]讲解@CallerSensitive注解
本文介绍了 `@CallerSensitive` 注解及其作用,通过 `Reflection.getCallerClass()` 方法返回调用方的 Class 对象。文章还详细解释了如何通过配置 VM Options 使自定义类被启动类加载器加载,以识别该注解。涉及的 VM Options 包括 `-Xbootclasspath`、`-Xbootclasspath/a` 和 `-Xbootclasspath/p`。最后,推荐了几篇关于 ClassLoader 的详细文章,供读者进一步学习。
30 12
|
28天前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第19天】本文介绍了Java编程中重要的数据结构——Map,通过问答形式讲解了Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的使用和性能优化技巧,适合初学者和进阶者学习。
44 4
|
1月前
|
前端开发 小程序 Java
java基础:map遍历使用;java使用 Patten 和Matches 进行正则匹配;后端传到前端展示图片三种情况,并保存到手机
这篇文章介绍了Java中Map的遍历方法、使用Pattern和matches进行正则表达式匹配,以及后端向前端传输图片并保存到手机的三种情况。
20 1
|
18天前
|
Java 编译器
Java进阶之标准注解
Java进阶之标准注解
28 0
|
1月前
|
Oracle Java 关系型数据库
|
1月前
|
JSON Java 数据库
java 常用注解大全、注解笔记
关于Java常用注解的大全和笔记,涵盖了实体类、JSON处理、HTTP请求映射等多个方面的注解使用。
35 0
java 常用注解大全、注解笔记
下一篇
无影云桌面