注解是 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 还能干啥?

不吹牛逼,撸个注解有什么难的相关推荐

  1. @jsonfield注解_好了,不装了,撸个注解有什么难的!

    文章来源:https://mp.weixin.qq.com/s/z8qA8i3F8-SDbcTDMdJgjw 作者:沉默王二 注解是 Java 中非常重要的一部分,但经常被忽视也是真的.之所以这么说是 ...

  2. 撸个注解有什么难的 请看这里

    01.注解是什么 注解(Annotation)是在 Java 1.5 时引入的概念,同 class 和 interface 一样,也属于一种类型.注解提供了一系列数据用来装饰程序代码(类.方法.字段等 ...

  3. 教妹学 Java:撸个注解有什么难的

    "二哥,这节讲注解吗?"三妹问. "是的."我说,"注解是 Java 中非常重要的一部分,但经常被忽视也是真的.之所以这么说是因为我们更倾向成为一名注 ...

  4. Spring注解浅入浅出——不吹牛逼不装逼

    Spring注解浅入浅出--不吹牛逼不装逼 前情提要 上文书咱们说了<Spring浅入浅出>,对Spring的核心思想看过上篇的朋友应该已经掌握了,此篇用上篇铺垫,引入注解,继续深入学习. ...

  5. Spring MVC浅入浅出——不吹牛逼不装逼

    Spring MVC浅入浅出--不吹牛逼不装逼 前言 上文书说了Spring相关的知识,对Spring来了个浅入浅出,大家应该了解到,Spring在三层架构中主做Service层,那还有Web层,也就 ...

  6. Spring的数据库编程浅入浅出——不吹牛逼不装逼

    Spring的数据库编程浅入浅出--不吹牛逼不装逼 前言 上文书我写了Spring的核心部分控制反转和依赖注入,后来又衔接了注解,在这后面本来是应该写Spring AOP的,但我觉得对于初学者来说,这 ...

  7. Spring浅入浅出——不吹牛逼不装逼

    Spring浅入浅出--不吹牛逼不装逼 前言: 今天决定要开始总结框架了,虽然以前总结过两篇,但是思维是变化的,而且也没有什么规定说总结过的东西就不能再总结了,是吧.这次总结我命名为浅入浅出,主要在于 ...

  8. 青龙羊毛——灰兔掌赚吹牛逼

    本篇博文更新三个毛,两个soy大佬的悬赏平台毛(其实是两个APP同一平台),一个tom大佬的滴滴果园毛!该博文完全搬运的两位大佬的原话! 1.灰兔 (1)码子 灰兔--兔兔 作者码子:AQ222731 ...

  9. 在抖音上,你只要会装穷,会装富,会吹牛逼,会包装自己,会扯淡,那翻身就指日可待了

    在抖音上,你只要会装穷,会装富,会吹牛逼,会包装自己,会扯淡,那翻身就指日可待了,这些看着简单,做起来还是有些难度的,这需要强大的,抗击打能力,毕竟很多群众,眼睛还是雪亮的,你得厚着脸皮,厚颜无耻的, ...

最新文章

  1. 1012 The Best Rank
  2. 【组队学习】【31期】 吃瓜教程——西瓜书+南瓜书
  3. LINUX应用与发展简介
  4. MySQL:实用 SQL 语句集合
  5. 任务计划cron、服务管理工具chkconfig/systemctl
  6. python工作-python工作中总结
  7. 性能之外:LSI 6Gb/s SAS RAID渠道先行
  8. 单链表的头插法和尾插法c语言实现
  9. 阿里专家杜万:Java响应式编程,一文全面解读
  10. sqlmap --os-shell反制小思路
  11. 【LeetCode】剑指 Offer 19. 正则表达式匹配
  12. 函数和常用模块【day04】:内置函数(十)
  13. vue 创建图片坐标点_Vue Echarts 显示地图且根据坐标设置标注点
  14. 解决“配置系统未能初始化”问题
  15. 网络安全等级保护二级产品清单整理
  16. 微波雷达感应模块,智能马桶传感方案,智能化生活
  17. 干货 | DDD实战:基于洋葱模型的分层代码架构设计
  18. mvn编译“Cannot find matching toolchain definitions for the following toolchain types“报错解决方法
  19. python 中的 __repr__() 方法
  20. 【计算机网络】计算机网络常用网络命令

热门文章

  1. percona-toolkit--pt-table-checksum
  2. Docker容器网络
  3. 13 张图带你学懂 Kubernetes Service(转载)
  4. 一次面试总结(记录)
  5. onActivityResult()后onresume()
  6. js 实现网页显示倒计时
  7. Gartner: 2017年11大信息安全技术(解读版)
  8. Tcl与Design Compiler (十二)——综合后处理
  9. (转) Twisted :第十九部分 改变之前的想法
  10. hadoop-0.21.0-eclipse-plugin无法在eclipse中运行解决方案