学java注解,看这一篇文章就够了

news2025/1/13 20:01:20

一、注解的概念

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 注解的作用:

1、生成文档,通过代码里标识的元数据生成javadoc文档。

2、编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。(源码时注解)

3、编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。 (编译时注解)

4、运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。(运行时注解)

三、注解原理及分类

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的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等。

TYPE, // 类、接口、枚举类

FIELD, // 成员变量(包括:枚举常量)

METHOD, // 成员方法

PARAMETER, // 方法参数

CONSTRUCTOR, // 构造方法

LOCAL_VARIABLE, // 局部变量

ANNOTATION_TYPE, // 注解类

PACKAGE, // 可用于修饰:包

TYPE_PARAMETER, // 类型参数,JDK 1.8 新增

TYPE_USE // 使用类型的任何地方,JDK 1.8 新增

  • @Retention

相当于一个时间戳。

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

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

取值如下:

RetentionPolicy.SOURCE:注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视,这类注解不会被编译进入.class文件

RetentionPolicy.CLASS:注解只被保留到编译进行的时候,它并不会被加载到JVM中,这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中

RetentionPolicy.RUNTIME:注解可以保留到程序运行的时候,会被加载进入到JVM中,所以程序运行时可以获取到。

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

  • @Documented

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

  • @Inherited

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

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

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

  • @Repeatable (Java8)

重复注解

四、自定义注解例子

4.1 手写ButterKnife框架(使用运行时注解的方式)

其核心思想是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<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源文件 这个框架功能非常实用.

4、定义路由管理类,路由管理器,辅助完成交互通信

分别用于存储每个module及module下的路径

使用:

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/501649.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Powered by 平行云 | 我们与大有艺术一起,在玩儿一种很新的戏剧

可以用VR看的&#xff0c;才是真元宇宙&#xff01; 平行云赋能大有艺术&#xff0c;开启孟京辉导演的元宇宙「沉浸式」戏剧《浮士德》。"全球第一场可以用VR看的元宇宙戏剧"是什么的体验&#xff1f; 让我们一起一探究竟&#xff01; 你慢慢地走进了一片无边无际的…

巧用千寻位置GNSS软件| 铁路放样必备技巧

铁路放样是在铁路测量施工前的一项重要工作&#xff0c;千寻位置GNSS软件可轻松实现铁路放样&#xff0c;本期就和大家分享具体操作步骤。 点击【测量】->【铁路放样】&#xff0c;在线路库中选择一条线路点击【确定】直接进入线路中 心线放样&#xff0c;如图 5.16-1所示。…

实现方法、构造器位置的自动装配

1.Autowired注解概述 Autowired注解的源码 package org.springframework.beans.factory.annotation;import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPoli…

Uint8Array文件下载

vue项目&#xff0c;自己调用了阿里云OSS的SDK&#xff0c;进行文件的上传以及下载&#xff0c;有个功能是列表中的附件需要支持点击下载&#xff0c;这里就用到阿里云oss的文件下载&#xff0c;使用了Nodejs的SDK const OSS require(ali-oss); const client new OSS({...op…

nodejs+vue网上学生社团管理系统

并运用Photoshop CS6技术美化网页&#xff0c;辅之以CSS技术。系统是基于面向对象编程的web应用程序。本系统主要实现的功能有系统用户管理、社团信息管理、社团类别管理、社团活动管理、社团论坛管理、系统管理、个人资料管理、学生入团管理、社团公告管理、社团活动管理、社团…

Ubuntu22.04安装opencv依赖包libjasper-dev出现unable to locate libjasper-dev的终极解决办法

执行 sudo apt install libjasper-dev 提示 执行 sudo apt install libjasper1 libjasper-dev 仍然提示 更换软件源&#xff1a; sudo add-apt-repository "deb http://security.ubuntu.com/ubuntu xenial-security main" sudo apt-get update提示 该仓库没有数…

tiechui_lesson05_内核小文件拷贝

主要学习在内核中的文件操作&#xff0c;包括文件的打开&#xff0c;创建&#xff0c;读取&#xff0c;写入&#xff0c;查询文件属性等。 涉及的API和宏函数 ZwOpenFileZwCreateFileZwQueryInformationFileZwReadFileZwWriteFileZwCloseInitializeObjectAttributes 1.文件的…

学习日记,java学习

题目 生病了,可能是羊了,喉咙好干,好难受 每日一题 1010. 总持续时间可被 60 整除的歌曲 难度中等268收藏分享切换为英文接收动态反馈 在歌曲列表中&#xff0c;第 i 首歌曲的持续时间为 time[i] 秒。 返回其总持续时间&#xff08;以秒为单位&#xff09;可被 60 整除的…

vue+elementui在线教学互动学习考试系统nodejs师生互动交流网站kpm58

语言 node.js 框架&#xff1a;Express 前端:Vue.js 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat 开发软件&#xff1a;VScode 前端vueelementui, (1) vue引入elementui 1.使用npm安装element-ui npm i element-ui -S 2.在main.js中导入使用vue import element-u…

Android WorldWind的使用与加载数方瓦片

明确下定义&#xff0c;数方瓦片也叫五层十五级瓦片。现记录下如何在World Wind Android中加载数方瓦片的方法。 原理 加载数方瓦片最核心的方法是WorldWindAndroid加载图片的方法&#xff0c;更加详细的使用说明请访问worldwind官网。核心代码如下&#xff1a; Sector sect…

Daimayuan Online Judge #613. 好序列(思维题 暴力/启发式分裂)

题目 思路来源 知乎严格鸽 (暴力/启发式分裂)代码源每日一题 Div1 好序列 - 知乎 题解 启发式分裂&#xff0c;可以认为是启发式合并的逆过程 比较直白的想法是找到第一个只出现一次的数的位置x&#xff0c;然后分治[1,x-1]和[x1,n] 但是这样最坏复杂度是O(n^2)的&#xf…

数字化转型对企业的好处有哪些(上)

过时的技术、流程和客户关系管理困难是当前阻碍企业增长的主要障碍&#xff0c;CRM系统是企业数字化转型的主要工具&#xff0c;帮助企业简化操作、提高效率&#xff0c;为客户提供更加卓越的客户体验。下面说说&#xff0c;企业数字化转型的十大好处。 放眼全球来看&#xff…

第四十三章 Unity 开关 (Toggle) UI

本章节我们介绍开关 (Toggle)和开关组 (Toggle Group)。首先&#xff0c;我们点击菜单栏“GameObject”->“UI”->“Toggle”&#xff0c;然后调整它的位置&#xff0c;效果如下所示 相信大家在很多网页中也看到过类似的UI元素&#xff0c;它通常用于让用户勾选某些选项。…

计算机二级精选习题精讲精练

请点击↑关注、收藏&#xff0c;本博客免费为你获取精彩知识分享&#xff01;有惊喜哟&#xff01;&#xff01; 1、在线性表的链式存储结构中&#xff0c;其存储空间一般是不连续的&#xff0c;并且( ) A&#xff09;前件结点的存储序号大于后件结点的存储序号 B&#x…

java排序算法精讲

排序算法 概要一、冒泡排序概念实现步骤 代码 二、选择排序概念实现步骤 代码 三、插入排序概念实现步骤 代码 四、快速排序概念实现步骤 代码 五、归并排序概念实现步骤 代码 六、堆排序概念实现步骤 代码 总结以二维表表现出各个排序的关系 概要 Java是一种面向对象的编程语言…

一篇文章带您区分GNSS欺骗模拟测试的两种方式

写在前面 注意&#xff1a;提供的设备与案例、使用指南等指导性文件是为了在测试环境中对接收机的抗干扰能力进行验证&#xff0c;而非出于欺骗或干扰真实环境中的GNSS信号的目的&#xff01;请确保通过线缆连接应用或暗室应用&#xff0c;若因为违规使用产生的任何法律后果和…

携带数据的Ajax POST请求

前端页面代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <title>发送ajax POST请求 看如何携带数据</title> <script type"text/javascript"> …

MES管理系统有什么功能?前期实施MES需要做些什么

MES系统是在制造业数字化的环境下&#xff0c;围绕生产制造执行而开发的一套生产管理系统。它以车间为管理核心&#xff0c;通过集成各信息系统&#xff0c;整合企业资源&#xff0c;实现从订单下达到产品完成的整个生产制造过程的数字化管理。 MES系统在实施前需要进行各种准备…

“UTONMOS吧”的出现,能否唤起百度贴吧曾经的辉煌?

百度贴吧&#xff0c;诞生于一个中文互联网尚且信息不足的时代&#xff0c;并建立起了一个庞大的社区。然而进入自媒体时代&#xff0c;贴吧这个大广场日渐冷清&#xff0c;2015年月活用户超过3亿&#xff0c;到2021年已不足4000万。 梗的流行&#xff0c;带动一大批人开始网上…

OpenShift 4 - KubeVirt 虚机使用的磁盘和卷

《OpenShift / RHEL / DevSecOps 汇总目录》 说明&#xff1a;本文已经在 OpenShift 4.12 环境中验证 文章目录 KubeVirt 虚机的磁盘和卷Disk 磁盘Volume 卷 磁盘和卷示例containerDisk 卷示例cloudInitNoCloud 卷示例dataVolume 卷示例基于容器镜像基于 qcow2 文件基于 DataSo…