学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-463459.html

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

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

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

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

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

相关文章

  • tmux 使用看这一篇文章就够了

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

    2024年02月02日
    浏览(58)
  • 初级面试问到rabbitMQ,看这一篇文章就够了!

    一、rabbitMQ的基础要点回顾 1.使用场景 1) 解耦: 使用消息队列避免模块间的直接调用。将所需共享的数据放在消息队列中,对于新增的业务模块,只要对该类消息感兴趣就可以订阅该消息,对原有系统无影响,降低了各个模块的耦合度,提供系统的扩展性。 2) 异步: 消息

    2024年02月04日
    浏览(51)
  • 关于腾讯云轻量应用服务器性能测评,看这一篇文章就够了

    腾讯云轻量应用服务器性能如何?为什么便宜是不是性能不行?腾讯云百科txybk.com从轻量应用服务器的CPU型号、处理器主频、内存、公网带宽、月流量和系统盘多方面来详细测评轻量性能,轻量应用服务器性价比高,并不是性能不行,只是限制每月流量,轻量还是很值得买的

    2024年02月08日
    浏览(46)
  • Python SQL 数据库操作利器:SQLAlchemy 库详解(看这一篇文章就够了)

    引言: Python 是一门广受欢迎的编程语言,而 SQL 则是用于管理和操作数据库的标准查询语言。SQLAlchemy 是一个功能强大的 Python 库,它提供了一种与多种数据库进行交互的灵活方式。本文将介绍 SQLAlchemy 库,并以九个重要的要点详细解释其功能和用法。 SQLAlchemy 简介 SQLAlchem

    2024年02月07日
    浏览(66)
  • Python爬虫案例解析:五个实用案例及代码示例(学习爬虫看这一篇文章就够了)

    导言: Python爬虫是一种强大的工具,可以帮助我们从网页中抓取数据,并进行各种处理和分析。在本篇博客中,我们将介绍五个实用的Python爬虫案例,并提供相应的代码示例和解析。通过这些案例,读者可以了解如何应用Python爬虫来解决不同的数据获取和处理问题,从而进一

    2024年02月16日
    浏览(50)
  • 深入探索Python的scipy库:强大的科学计算工具集(学scipy看这一篇文章就够了)

    引言: Python是一种功能强大且受欢迎的编程语言,广泛应用于科学计算、数据分析和工程应用领域。在Python生态系统中,scipy库是一个重要的工具,提供了许多用于科学计算的高级功能。本文将深入探索scipy库,介绍其主要功能和用法,并提供相应的代码示例和相关资源。 一、

    2024年02月09日
    浏览(56)
  • java注解,一篇文章就够了

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

    2024年02月03日
    浏览(54)
  • 【Java】学JDBC看这篇文章就够了—JDBC保姆级教程

    目录 一、JDBC概述 基本介绍 JDBC相关API 二、连接mysql数据库 准备工作 JDBC程序编写步骤 五种连接数据库的方式  三、ResultSet(结果集) 基本介绍 四、Statement 和 PreparedStatement Statement PreparedStatement  五、事务 基本介绍 六、批处理 基本介绍 七、数据库连接池 传统获取Connectio

    2023年04月24日
    浏览(44)
  • DevOps是什么?只看这篇文章就够了!

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

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

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

    2024年01月16日
    浏览(45)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包