java注解,一篇文章就够了

这篇具有很好参考价值的文章主要介绍了java注解,一篇文章就够了。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、注解的概念

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)拿到我们想要的注解并取值

java注解,一篇文章就够了

下面是两个相关的概念:

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框架(使用运行时注解的方式)

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<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下的路径

使用:文章来源地址https://www.toymoban.com/news/detail-436494.html

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

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

到了这里,关于java注解,一篇文章就够了的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • RabbitMQ看这一篇文章就够了

    第1节 MQ是什么 第2节 MQ 的主要特点 第3节 RabbitMQ介绍 官网地址 第1节 安装步骤   第2节 用户管理 第1节 RabbitMQ的整体架构图 架构   Producer Exchange Queue Customer 一个消息(message)从开始到结束的过程 第1节 原理图 HelloWorld   P: producer 生产者 C: consumer 消费者 中间红色的部分是一个队

    2024年02月08日
    浏览(55)
  • Docker详细教程,一篇文章就够了

    假设你们公司正在秘密研发下一个“今日头条”APP,我们姑且称为明日头条,程序员自己从头到尾搭建了一套环境开始写代码,写完代码后程序员要把代码交给测试同学测试,这时测试同学开始从头到尾搭建这套环境,测试过程中出现问题程序员也不用担心,大可以一脸无辜

    2024年02月08日
    浏览(48)
  • 调教ChatGpt看这一篇文章就够了

    ChatGPT是OpenAI发布的基于GPT-3.5架构的大型语言模型,可用于各种自然语言处理任务,包括文本生成、对话系统、文本分类等。本文将为读者提供一份ChatGPT调教教程,帮助读者了解如何训练自己的ChatGPT模型,以便更好地满足自己的需求。 1. 安装必要的软件和工具 在开始之前,

    2024年02月01日
    浏览(44)
  • 搞懂TVS管,有这篇文章就够了

    摘要:本文主要介绍TVS的工作原理、关键参数和选型。 TVS(Transient Voltage Suppressors,瞬态电压抑制器)又称雪崩击穿二极管,是一种高效电路保护器件,主要是保护电路不受瞬态高压尖峰脉冲(静电或雷击浪涌)的冲击。 TVS是采用半导体工艺制成的单个PN结或多个PN结集成的器件,

    2023年04月08日
    浏览(54)
  • RabbitMQ入门到实战一篇文章就够了

    课程内容 认识RabbitMQ 安装RabbitMQ SpringBoot使用RabbitMQ 其他特性 1.RabbitMQ认识 1.1.RabbitMQ是什么 MQ全称为Message Queue,即消息队列. 它也是一个队列,遵循FIFO原则 。RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue Protocol高级消息队列协议)协议实现的消息队列,它是一种应用程

    2024年03月09日
    浏览(49)
  • MapReduce的工作原理这篇文章就够了

    MapReduce是一种分布式计算模型,用于处理大规模数据集。它将大规模数据集分成小的数据块,然后在分布式计算集群上并行处理这些数据块。MapReduce模型由Google公司提出,并在Hadoop等开源框架中得到了广泛应用。 MapReduce模型包含两个阶段:Map阶段和Reduce阶段。 Map阶段 在Map阶

    2024年02月06日
    浏览(42)
  • DevOps是什么?只看这篇文章就够了!

    作者:沈洲 原文链接:DevOps是什么?只看这篇文章就够了!-云社区-华为云 作为一个热门的概念,DevOps这个名词在程序员社区里频频出现,备受技术大佬们的追捧。甚至网络上有了“南无DevOps”的戏言(南无在梵语的意思是“皈依”),也侧面反映了DevOps的风靡。 然而,一

    2024年02月21日
    浏览(49)
  • tmux 使用看这一篇文章就够了

    tmux是一个终端复用工具,允许用户在一个终端会话中同时管理多个终端窗口,提高了终端使用效率,尤其在服务器上进行远程管理时更加实用。在tmux中,可以创建多个终端窗口和窗格,并在这些窗口和窗格之间自由切换,还可以在后台运行会话,即使在终端断开连接后也可

    2024年02月02日
    浏览(58)
  • JavaWeb入门看这一篇文章就够了

    第1节 什么是web 第2节 什么是JavaWeb   第1节 JavaWeb服务器是什么 第2节 常见的JavaWeb服务器介绍 Tomcat当前最流行的web容器(免费) jetty(免费) Weblogic(收费) 第3节 Tomcat服务器 tomcat服务器介绍 tomcat的目录结构   tomcat的常用命令 tomcat的用户管理 tomcat的服务配置管理 在一台服务器上配

    2024年02月03日
    浏览(65)
  • 关于电脑屏幕亮度的调整,看这篇文章就够了

    你可能需要定期更改屏幕亮度。当外面很亮的时候,你想把它调大,这样你就能看到。当你在黑暗的房间里时,你会希望它变暗,这样就不会伤害你的眼睛。降低屏幕亮度也有助于节省电力并延长笔记本电脑的电池寿命。 除了手动更改屏幕亮度外,Windows还可以通过多种方式

    2024年01月16日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包