不吹牛逼,撸个注解有什么难的

注解是 Java 中非常重要的一部分,但经常被忽视也是真的。之所以这么说是因为我们更倾向成为一名注解的使用者而不是创建者。@Override 注解用过吧?@Service 注解用过吧?但你知道怎么自定义一个注解吗?

恐怕你会摇摇头,摆摆手,不好意思地承认自己的确没有自定义过。

01、注解是什么

注解(Annotation)是在 Java 1.5 时引入的概念,同 class 和 interface 一样,也属于一种类型。注解提供了一系列数据用来装饰程序代码(类、方法、字段等),但是注解并不是所装饰代码的一部分,它对代码的运行效果没有直接影响(这句话怎么理解呢?),由编译器决定该执行哪些操作。

来看一段代码,我随便写的,除了打印到控制台的那句宣传语,其他都不重要,嘻嘻。

public class AutowiredTest {@Autowiredprivate String name;public static void main(String[] args) {System.out.println("沉默王二,一枚有趣的程序员");}
}

注意到 @Autowired 这个注解了吧?它本来是为 Spring 容器注入 Bean 的,现在被我无情地扔在了成员变量 name 的身上,但这段代码所在的项目中并没有启用 Spring,意味着 @Autowired 注解此时只是一个摆设。

我之所以举这个无聊的例子就是为了证明一个观点:注解对代码的运行效果没有直接影响,明白我的用意了吧?

02、注解的生命周期

注解的生命周期有 3 种策略,定义在 RetentionPolicy 枚举中。

1)SOURCE:在源文件中有效,被编译器丢弃。

2)CLASS:在编译器生成的字节码文件中有效,但在运行时会被处理类文件的 JVM 丢弃。

3)RUNTIME:在运行时有效。这也是注解生命周期中最常用的一种策略,它允许程序通过反射的方式访问注解,并根据注解的定义执行相应的代码。

03、注解装饰的目标

注解的目标定义了注解将适用于哪一种级别的 Java 代码上,有些注解只适用于方法,有些只适用于成员变量,有些只适用于类,有些则都适用。

截止到 Java 9,注解的类型一共有 11 种,定义在 ElementType 枚举中。

1)TYPE:用于类、接口、注解、枚举

2)FIELD:用于字段(类的成员变量),或者枚举常量

3)METHOD:用于方法

4)PARAMETER:用于普通方法或者构造方法的参数

5)CONSTRUCTOR:用于构造方法

6)LOCAL_VARIABLE:用于变量

7)ANNOTATION_TYPE:用于注解

8)PACKAGE:用于包

9)TYPE_PARAMETER:用于泛型参数

10)TYPE_USE:用于声明语句、泛型或者强制转换语句中的类型

11)MODULE:用于模块

04、开始撸注解

说再多,都不如撸个注解来得让人心动。撸个什么样的注解呢?一个字段注解吧,它用来标记对象在序列化成 JSON 的时候要不要包含这个字段。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface JsonField {public String value() default "";
}

1)JsonField 注解的生命周期是 RUNTIME,也就是运行时有效。

2)JsonField 注解装饰的目标是 FIELD,也就是针对字段的。

3)创建注解需要用到 @interface 关键字。

4)JsonField 注解有一个参数,名字为 value,类型为 String,默认值为一个空字符串。

为什么参数名要为 value 呢?有什么特殊的含义吗?

当然是有的,value 允许注解的使用者提供一个无需指定名字的参数。举个例子,我们可以在一个字段上使用 @JsonField(value = "沉默王二"),也可以把 value = 省略,变成 @JsonField("沉默王二")

default "" 有什么特殊含义吗?

当然也是有的,它允许我们在一个字段上直接使用 @JsonField,而无需指定参数的名和值。

05、使用注解

是骡子是马拉出来遛遛,对吧?现在 @JsonField 注解已经撸好了,接下来就到了怎么使用它的环节。

假设有一个作者类,他有 3 个字段,分别是 age、name 和 bookName,后 2 个是必须序列化的字段。

public class Writer {private int age;@JsonField("writerName")private String name;@JsonFieldprivate String bookName;public Writer(int age, String name, String bookName) {this.age = age;this.name = name;this.bookName = bookName;}// getter / setter@Overridepublic String toString() {return "Writer{" +"age=" + age +", name='" + name + '\'' +", bookName='" + bookName + '\'' +'}';}
}

1)name 上的 @JsonField 注解提供了显式的字符串值。

2)bookName 上的 @JsonField 注解使用了缺省项。

接下来,我们来编写序列化类 JsonSerializer,内容如下:

public class JsonSerializer {public static String serialize(Object object) throws IllegalAccessException {Class<?> objectClass = object.getClass();Map<String, String> jsonElements = new HashMap<>();for (Field field : objectClass.getDeclaredFields()) {field.setAccessible(true);if (field.isAnnotationPresent(JsonField.class)) {jsonElements.put(getSerializedKey(field), (String) field.get(object));}}return toJsonString(jsonElements);}private static String getSerializedKey(Field field) {String annotationValue = field.getAnnotation(JsonField.class).value();if (annotationValue.isEmpty()) {return field.getName();} else {return annotationValue;}}private static String toJsonString(Map<String, String> jsonMap) {String elementsString = jsonMap.entrySet().stream().map(entry -> "\"" + entry.getKey() + "\":\"" + entry.getValue() + "\"").collect(Collectors.joining(","));return "{" + elementsString + "}";}
}

JsonSerializer 类的内容看起来似乎有点多,但不要怕,我一点点来解释,直到你搞明白为止。

1)serialize() 方法是用来序列化对象的,它接收一个 Object 类型的参数。objectClass.getDeclaredFields() 通过反射的方式获取对象声明的所有字段,然后进行 for 循环遍历。在 for 循环中,先通过 field.setAccessible(true) 将反射对象的可访问性设置为 true,供序列化使用(如果没有这个步骤的话,private 字段是无法获取的,会抛出 IllegalAccessException 异常);再通过 isAnnotationPresent() 判断字段是否装饰了 JsonField 注解,如果是的话,调用 getSerializedKey() 方法,以及获取该对象上由此字段表示的值,并放入 jsonElements 中。

2)getSerializedKey() 方法用来获取字段上注解的值,如果注解的值是空的,则返回字段名。

3)toJsonString() 方法借助 Stream 流的方式返回格式化后的 JSON 字符串。如果对 Stream 流比较陌生的话,请查阅我之前写的 Stream 流入门。

看完我的解释,是不是豁然开朗了?

接下来,我们来写一个测试类 JsonFieldTest,内容如下:

public class JsonFieldTest {public static void main(String[] args) throws IllegalAccessException {Writer cmower = new Writer(18,"沉默王二","Web全栈开发进阶之路");System.out.println(JsonSerializer.serialize(cmower));}
}

程序输出结果如下:

{"bookName":"Web全栈开发进阶之路","writerName":"沉默王二"}

从结果上来看:

1)Writer 类的 age 字段没有装饰 @JsonField 注解,所以没有序列化。

2)Writer 类的 name 字段装饰了 @JsonField 注解,并且显示指定了字符串“writerName”,所以序列化后变成了 writerName。

3)Writer 类的 bookName 字段装饰了 @JsonField 注解,但没有显式指定值,所以序列化后仍然是 bookName。

06、鸣谢

好了,我亲爱的读者朋友,以上就是本文的全部内容了,是不是感觉撸个注解也没什么难的?你也赶紧动动小手试试吧!

END

IDEA 终于支持中文版和 JDK 直接下载了(太方便了)附新版介绍视频6大分布式定时任务对比除了负载均衡,Nginx 还能干啥?

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

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

相关文章

python二分法查找程序_Python程序查找地板划分

python二分法查找程序When we divide a number by another number – division operator (/) return quotient it may be an integer or float. But, when we need quotient without floating number – we can floor division (//) operator, it returns quotient (result) wi…

DOS命令(bat批处理脚本)遍历目录、遍历子目录下的文件、遍历数字和遍历文件内容

1.遍历目录 格式&#xff1a; for /d %%名称 in (路径) do 具体操作脚本测试&#xff1a;创建test.bat文件&#xff0c;*代表test.bat文件所在的当前目录 echo offfor /d %%a in (*) do (echo %%a)pause2.遍历目录和子目录下的文件 格式&#xff1a; for /r "目录路径&…

几位阿里朋友重写的Java并发编程,牛逼了

昨天在黄金时代群里和读者聊机械键盘大 F 的时候&#xff0c;好朋友 cxuan 推了一篇文章&#xff0c;吸引了我的眼球&#xff0c;名叫“太赞了&#xff0c;阿里几位工程师重写了 《Java 并发编程》”&#xff0c;我看完后&#xff0c;直呼“牛逼了”&#xff0c;就想着赶紧推荐…

FFmpeg 2.1 试用(新版支持HEVC,VP9)

2019独角兽企业重金招聘Python工程师标准>>> 前两天帮一位老师转码图像的时候&#xff0c;无意间发现新版FFmpeg竟然支持了下一代编码标准HEVC&#xff0c;以及Google提出的下一代编码标准VP9。真心没想到FFmpeg对下一代的编码标准支持的是如此之快。我还以为这两种…

电源变换适用于非独立源码_适用于非None测试的Python程序

电源变换适用于非独立源码As we have discussed in the previous post (Python None keyword), that "None" is a keyword which can be used to assign a null value to a variable or to check whether a variable contains any value or not. 正如我们在上一篇文章…

Linus shell 在一个脚本中调用另外一个脚本变量

1.新建public.sh文件&#xff0c;并添加以下内容&#xff1a; 2.新建ceshi.sh文件&#xff0c;并添加以下内容&#xff1a; 3.在终端赋予ceshi.sh文件执行权限&#xff0c;并运行该文件。

史上最全的延迟任务实现方式汇总!附代码(强烈推荐)

这篇文章的诞生要感谢一位读者&#xff0c;是他让这篇优秀的文章有了和大家见面的机会&#xff0c;重点是优秀文章&#xff0c;哈哈。 事情的经过是这样的... 不用谢我&#xff0c;送人玫瑰&#xff0c;手有余香。相信接下来的内容一定不会让你失望&#xff0c;因为它将是目前…

linux定时任务生产java服务无法执行问题群友案例

linux定时任务crond export变量问题群友案例来自网友兄弟 北京Grady(254553457) 的总结。1&#xff09;我写了一个重启resin的脚本&#xff0c;由于业务原因&#xff0c;需要定时在某一个时间重启下resin服务器&#xff0c;于是就在crontab里配置了如下内容&#xff1a;50 17 *…

在Python中执行while循环

Python执行while循环 (Python do while loop) Like other programming languages, do while loop is an exit controlled loop – which validates the test condition after executing the loop statements (loop body). 像其他编程语言一样&#xff0c; do while循环是退出控…

Windows DOS命令 bat批处理脚本交互操作

set的主要作用是赋值 set /p optEnter your option:先显示Enter your option:&#xff0c;再接收用户输入的内容,以回车表示结束&#xff0c;赋值给变量opt 实例测试&#xff1a; echo offecho 1.show ip address echo 2.show directory:main set /p optEnter your option:i…

面经分享:历时半个月,终于拿到了蚂蚁金服的offer!

在今天&#xff0c;我收到了蚂蚁金服A级的实习录用offer。从开始面试到拿到口头offer&#xff08;四面技术一面HR&#xff09;战线大约拉了半个月&#xff0c;从拿到口头offer到收到正式录用邮件大概又是半个月。思前想后&#xff0c;决定还是做一个整理与总结。一方面是回顾并…

将String转换成InputStream

String str "";//add your string contentInputStream inputStream new ByteArrayInputStream(str.getBytes());

bat批处理脚本获取window系统所有用户名并设置密码,禁用Guest账户

net user可以获取系统用户名&#xff0c;如下&#xff1a; 可以编写代码&#xff0c;bat批处理脚本获取window系统所有用户名并设置密码&#xff0c;如下&#xff0c;如果bat文件有中文&#xff0c;在cmd中执行会出现乱码&#xff0c;解决方法是用记事本打开bat文件&#xff0…

Python中的Dask数组

Python Dask数组 (Python Dask Array) Dask is parallel computing python library and it is mainly used to run across multiple systems. Dask is used to process the data efficiently on a different cluster of machines. Dask can completely use all the cores avail…

Spring IoC?看这篇文章就够了...

前言刚开始听到 IoC&#xff0c;会觉得特别高大上&#xff0c;但其实明白原理了很简单。跟着我的脚步&#xff0c;一文带你吃透 IoC 原理。本文围绕 是何、为何、如何 来谈&#xff1a;是何上一篇文章有同学问我在官网该看哪些内容&#xff0c;怎么找的&#xff0c;那今天的截图…

交换机arp转ip-mac绑定命令工具-免费版

一、简介 为了防止ARP攻击&#xff0c;我们经常需要在三层交换机上做IP地址与MAC地址的绑定操作。 先要进入System-View系统视图模式&#xff0c;输入"sys"即可。 system-view: [huawei]arp static 192.168.60.58 7813-3a79-d0aa在交换机上使用dis arp命令可以查看…

《纵向切入ASP.NET 3.5控件和组件开发技术》笔记:高效率事件集合对象

在之前讲的几个例子中&#xff0c;使用的是最普通的定义事件方法&#xff0c;比如KingTextBox中事件是这样定义的&#xff1a;/// <summary>/// 获得本书更多内容,请看:/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx/// </summary>public e…

html 链接 id属性_HTML id属性

html 链接 id属性The id attribute is used to specify a unique id for an element in HTML. This id cannot be used for multiple elements in HTML. You can add the id to any HTML element. id属性用于为HTML中的元素指定唯一的ID 。 该ID不能用于HTML中的多个元素。 您可…

老大说:谁要再用double定义商品金额,就自己收拾东西走

先看现象涉及诸如float或者double这两种浮点型数据的处理时&#xff0c;偶尔总会有一些怪怪的现象&#xff0c;不知道大家注意过没&#xff0c;举几个常见的栗子&#xff1a;典型现象&#xff08;一&#xff09;&#xff1a;条件判断超预期System.out.println( 1f 0.9999999f …

用python做采集时相对路径转换成绝对路径

采集时&#xff0c;有时候需要采集图片&#xff0c;但某些网站的图片提供的相对地址&#xff0c;最好转换成绝对地址在scrapy中有如下的解决策略http://stackoverflow.com/questions/6499603/python-scrapy-convert-relative-paths-to-absolute-paths http://stackoverflow.com…