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

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

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


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




01、注解是什么


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


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


public class AutowiredTest {
    @Autowired
    private 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;
    @JsonField
    private String bookName;
    public Writer(int age, String name, String bookName) {
        this.age = age;
        this.name = name;
        this.bookName = bookName;
    }
    // getter / setter
    @Override
    public 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、鸣谢


好了,我亲爱的读者朋友,以上就是本文的全部内容了,是不是感觉撸个注解也没什么难的?你也赶紧动动小手试试吧!原创不易,莫要白票,请你为本文点赞个吧,这将是我写作更多优质文章的最强动力。


非常感谢大家的阅读!谢谢!


记得之前总结过一篇 Java 的自学路线,洋洋 3 万多字,但没有得到 CSDN 的流量加持,阅读的人数不多,但我只能说,读到的同学都赚到了。贴一张这篇文章下面的留言大家就明白了。


相关文章
|
XML Java 应用服务中间件
【Servlet入门】一篇文章让你从没听过到了熟于心
上一篇我们学习了Java Web中的XML部分了,如果你对XML一无所知,那你可以去看看这一篇文章,它可以使你快速入门XML。
【Servlet入门】一篇文章让你从没听过到了熟于心
|
存储 缓存 JavaScript
想好怎么学 Servlet规范了嘛?想好了嘛?没想好先看看这篇文章(爆肝之作),先看着然后慢慢想!!
Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容。 狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。Servlet运行于支持Java的应用服务器中。从原理上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。
想好怎么学 Servlet规范了嘛?想好了嘛?没想好先看看这篇文章(爆肝之作),先看着然后慢慢想!!
|
存储 XML Java
教你手撸一个简单IOC
教你手撸一个简单IOC
|
9月前
|
XML Java 数据格式
🚀今天,我们来详细的聊一聊SpringBoot自动配置原理,学了这么久,你学废了吗?
🚀今天,我们来详细的聊一聊SpringBoot自动配置原理,学了这么久,你学废了吗?
131 0
|
Java API Spring
面试官:小伙子,给我说一下spring框架吧
云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 1. spring是什么 轻量级开源框架以IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核。
面试官:小伙子,给我说一下spring框架吧
|
JavaScript
每日一学(一)
    以下两段代码各自的输出结果是什么,为什么。      //1. var a="window"; function Test(){ console.
882 0
|
8月前
|
缓存 Java 程序员
你能不能手敲出Spring框架?
Spring最成功的地方在于创始人Rod Johnson提出的,反而不是其本身的技术。技术上今天可以有Spring春天,明天就可以有Autumn秋天。核心理念有多重要?就如1871年巴黎公社的失败。公社在对抗法国zf和普鲁士占领军的背景下成立,最初成功掌握了巴黎。然而,,加上对外部威胁的应对不足,公社最终被镇压,存在时间不足可怜的三个月。本文收录在我开源的《Java学习面试指南》中,一份覆盖Java程序员所需掌握的Java核心知识、面试重点。希望收到大家的 ⭐ Star ⭐支持。
101 1
你能不能手敲出Spring框架?
|
设计模式 缓存 NoSQL
专治不会看源码的毛病--spring源码解析AOP篇
总结一下要形成的习惯:   1>有空时隔一段时间要做几道算法题,C语言和JAVA都可以,主要是训练思维。 2>定期阅读spring的源码。因为spring是框架,重设计,能够培养大局观。   3>阅读底层的书籍,如linux方面,虚拟机方面,这是内功。越高级的语言只是招式。   4>不要忘记做了一半的东西,如搜索引擎方面,redis方面,可以过一段时间再做,因为到时候自己的境界有提升,深入程度也会有所增加。      下面是今天的正题。我也很菜,看源码也很费力,所以都会从最容易的入手。先了解其原理,再去看源码。看源码看熟了,以后再遇到问题,就可以通过源码去了解原理了。
专治不会看源码的毛病--spring源码解析AOP篇
|
缓存 监控 Java
十.Spring源码剖析-拜托面试官别再问我AOP原理了
IOC/DI , AOP 是Spring最重要的两个特性 ,也是面试高频被问到的部分,前面我们已经分析了Spring的IOC相关源码以及DI依赖注入相关源码,从本篇文章开始我们着手分析Spring的AOP源码 。 开始之前,你需要对AOP 原理,JDK动态代理,CGLIB动态代理有一定的理解。这里先上一个图,后面源码分析的时候可以看着图来
|
Java Spring 安全
第四章:重新来认识你的老朋友Spring框架
欢迎查看Java开发之上帝之眼系列教程,如果您正在为Java后端庞大的体系所困扰,如果您正在为各种繁出不穷的技术和各种框架所迷茫,那么本系列文章将带您窥探Java庞大的体系。本系列教程希望您能站在上帝的角度去观察(了解)Java体系。
29617 0

热门文章

最新文章

相关实验场景

更多