前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 注解 —— 注解的理解、注解的使用与自定义注解

Java 注解 —— 注解的理解、注解的使用与自定义注解

作者头像
剑影啸清寒
发布2019-05-26 10:10:25
1.7K0
发布2019-05-26 10:10:25
举报

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1434912

Java 注解 —— 注解的理解、注解的使用与自定义注解

参考网址:

《秒懂,Java 注解 (Annotation)你可以这样学》

《Java注解基本原理》

《注解Annotation实现原理与自定义注解例子》

《框架开发之Java注解的妙用》


一. 注解基本介绍

1.1 什么是注解?

什么是注解?严谨的来说,注解提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。

Java 注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。注解包含在 java.lang.annotation 包中。

具体定义如下:

注解 (Annotation),也叫元数据。一种代码级别的说明。它是 JDK1.5 及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。 ——摘自百度百科

上面的说明虽然严谨,但比较难懂。笔者认为《秒懂,Java 注解 (Annotation)你可以这样学》一文中,作者 frank909 大佬的解释十分亲民:可以完全将注解当做生活中我们对人对物贴的标签

拿笔者最喜欢的一部动画电影来打个比方吧:《Zootopia》。《Zootopia》整个电影将动物们拟人化,性格各异。不管是兔子,狐狸,羚羊,豹子等等,每个动物都有一张固有标签:兔子乖巧,狐狸狡黠,羚羊温顺,豹子凶猛

但它们又有着自己真实的性格:想当警察的兔子,狡黠却不失善良的狐狸,披着狼皮的腹黑羚羊,吃着甜甜圈有少女心的豹子

《Zootopia》这个电影的内核是在讲,我们要试图冲破外界对自己所贴的标签的限制。但在这里笔者要稍微的当一下杠精,吹一下标签的作用:贴标签是较为精准的了解一个事物的最高效率方法。疯狂动物城中的动物们,外界对他们的第一印象,往往都是直接引用了该物种性格的固有标签。同样的在 Java 中,注解的作用就是告诉开发人员,被注解的内容是用来做什么的,换句话说,注解就是 Java 代码的标签。

在 Java 中,给代码贴合适的标签是很重要的,它很大程度的提高了效率。虽然写代码的时候开发人员也可以致敬《Zootopia》主旨,尝试突破标签的限制(比如给实现了 @Controller 功能的代码加了 @Service 注解),但笔者不保证写下这样代码开发人员的后续人身安全,太睿智的人肯定是要被针对的……

1.2 注解的作用

  1. 能够读懂别人写的代码(尤其是框架相关的代码);
  2. 实现替代配置文件的功能。比如可能原本需要很多配置文件以及很多逻辑才能实现的内容,如果使用合理的注解,就可以使用一个或多个注解来实现相同的功能。这样就使得代码更加清晰和整洁;
  3. 编译时进行格式检查
- 如 @Override 注解放在方法前,如果该方法不是覆盖了某个超类方法,编译的时候编译器就能检查出来。
- 做技术的怎么可以没有一点用技术吹牛逼的心理呢?如果会在合适的地方恰好的使用注解或者自定义注解的话,老板肯定会双手送你 666 的。当然笔者现在只是初学而已,距离用技术吹牛逼的道路还远。

1.3 注解的原理

注解本质是一个继承了 Annotation 的特殊接口,其具体实现类是 Java 运行时生成的动态代理类。而我们通过反射获取注解时,返回的是 Java 运行时生成的动态代理对象 $Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用 AnnotationInvocationHandler 的 invoke 方法。该方法会从 memberValues 这个 Map 中索引出对应的值。而 memberValues 的来源是 Java 常量池。 ——摘自《注解Annotation实现原理与自定义注解例子》

这里涉及的内容比较深入,笔者目前不能理解。先贴上来,以后慢慢来吧。

二. 元注解

元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。或者可以理解为:元注解也是一张标签,但是它是一张特殊的标签,它的作用和目的就是给其他普通的标签进行解释说明的

基本的元标签有 @Retention, @Documented, @Target, @Inherited 四种(后来到了 Java 8 又加入了 @Repeatable)。

2.1 @Retention

@Retention 定义了该注解的生命周期。当 @Retention 应用到一个注解上的时候,作用就是说明这个注解的存活时间。

  • RetentionPolicy.SOURCE: 注解只在源码阶段保留,在编译器完整编译之后,它将被丢弃忽视;
- 例:@Override, @SuppressWarnings

以 SpringMVC 中的 @Service 的源码为例:

package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
    String value() default "";
}

这里 @Service 拥有 @Retention(RetentionPolicy.RUNTIME) 注解,所以在程序运行时可以捕获到它们。

2.2 @Target

@Target 表示该注解用于什么地方,可以理解为:当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。可以使用的 ElementType 参数:

  • ElementType.CONSTRUCTOR: 对构造方法进行注解;
  • ElementType.ANNOTATION_TYPE: 对注解进行注解;
  • ElementType.FIELD: 对属性、成员变量、成员对象(包括 enum 实例)进行注解;
  • ElementType.LOCAL_VARIABLE: 对局部变量进行注解;
  • ElementType.METHOD: 对方法进行注解;
  • ElementType.PACKAGE: 对包进行注解;
  • ElementType.PARAMETER: 对描述参数进行注解;
  • ElementType.TYPE: 对类、接口、枚举进行注解;

如上面的 @Service 所示,@Service 的 @Target 注解值为 ElementType.TYPE,即 @Service 只能用于修饰类。

2.3 @Documented

@Documented 是一个简单的标记注解,表示是否将注解信息添加在 Java 文档,即 Javadoc 中。

2.4 @Inherited

Inherited 是指继承,@Inherited 定义了一个注释与子类的关系。如果一个超类带有 @Inherited 注解,那么对于该超类,它的子类如果没有被任何注解应用的话,那么这个子类就继承了超类的注解。

《秒懂,Java 注解 (Annotation)你可以这样学》一文中的例程与解释来说明:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}


@Test
public class A {}


public class B extends A {}

注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。可以这样理解: 老子非常有钱,所以人们给他贴了一张标签叫做富豪。 老子的儿子长大后,只要没有和老子断绝父子关系,虽然别人没有给他贴标签,但是他自然也是富豪。 老子的孙子长大了,自然也是富豪。 这就是人们口中戏称的富一代,富二代,富三代。虽然叫法不同,好像好多个标签,但其实事情的本质也就是他们有一张共同的标签,也就是老子身上的那张富豪的标签。

2.5 @Repeatable

@Repeatable 是 Java 8 中加入的,是指可重复的意思。通常使用 @Repeatable 的时候指注解的值可以同时取多个。依旧用《秒懂,Java 注解 (Annotation)你可以这样学》一文中的例程与解释来说明:一个人既是程序员,又是产品经理,同时也是画家。

@interface Persons {
    Person[] value();
}

@Repeatable(Persons.class)
@interface Person {
    String role default "";
}

@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan {
    ...
}

上面的代码通过 @Repeatable 定义了 Person,而 @Repeatable 后面括号的类相当于一个容器注解。容器注解就是用来存放其它注解的地方,它本身也是一个注解。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    Class<? extends Annotation> value();
}

上面是 @Repeatable 的源码。按照规定,如果使前面的 Persons 里面可以重复调用某个注解,则 Persons 必须有一个 value 的属性,且属性类型必须为被 @Repeatable 注解的 Person

三. 注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以无形参的方法形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。以下面的例程为例:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Coder {
    int id();
    String name();
    String language();
    String company();
}

上面假设定义了一个名为 @Coder 的注解,该注解有 id, name, language, company 三个属性。使用的时候,我们应该对其赋值。赋值的方式类似于 key=”value” 的方式进行,属性之间用 “,” 隔开:

@Coder(id = 10086, name = "GRQ", language = "JAVA", company = "cetc")
public class coderGRQ() {

}

此外,注解可以有默认值,需要用 default 关键字指定。例如上例:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Coder {
    public int id() default -1;
    public String name() default "GRQ";
    public String language() default "C++";
    public String company() default "China_Company";
}

如果:

@Coder
public class coderXX {}

由于在 @Coder 注解中设置了默认值,所以就不需要再 @Coder 后面的括号里进行赋值了。

此外,如果注解内只有一个名为 value 的属性时,应用该属性时可以将值直接写到括号内,不用写 value = “…”。例如:

public @interface language {
    String value();
}

那么下面两种声明是相同的:

// 第一种声明
@language("JAVA")
int coderA;
// 第二种声明
@language(value = "JAVA")
int coderA;

四. 常用注解

Java 中自带且常用的几种注解有 @Override, @Deprecated, @SuppresWarninngs, @SafeVarargs。

@Override 是一个标记类型注解,用于提示子类要复写父类中被 @Override 修饰的方法,它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种注解在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示。

@Deprecated 也是一个标记类型注解,用于标记过时的元素。比如如果开发人员正在调用一个过时的方法、类或成员变量时,可以用该注解进行标注。

@SuppressWarnings 并不是一个标记类型注解,它可以阻止警告的提示。它有一个类型为 String[] 的成员,其值为被禁止的警告名。

@SafeVarargs 是一个参数安全类型注解。它的目的是提醒开发人员,不要用参数做一些不安全的操作。它的存在会阻止编译器产生 unchecked 的警告。例如对于可变长度参数,如果和泛型一起使用,会产生比较多的编译器警告。如下面的方法:

public static <T> T useVarargs(T... args) {  
    return args.length > 0 ? args[0] : null;  
} 

如果参数传递的是不可具体化的类型(类似于 List 的泛型类型),每调用一次该方法,都会产生警告信息。如果希望禁止这个警告信息,可以使用 @SuppressWarnings(“unchecked”) 注解进行声明。同时在 Java 7 版本之后的 @SafeVarargs 注解针对 “unchecked” 警告进行了屏蔽,我们也可以用 @SafeVarargs 获得 @SuppressWarnings(“unchecked”) 同样的效果。

五. 自定义注解

此处参考《注解Annotation实现原理与自定义注解例子》的原理介绍和水果例程。

自定义注解类编写的规则:

  1. 注解类型定义为 @interface,所有的注解会自动继承 java.lang.Annotation 这一接口,而且不能再去继承其他的类或接口;
  2. 参数成员只能用 public 或 default 两个关键字修饰;
  3. 参数成员只能用基本类型:byte, short, char, int, long, float, double, boolean,以及 String, Enum, Class, Annotations 等数据类型,以及这些类型的数组;
  4. 要获取类方法和字段的注解信息,必须通过 Java 的反射技术
  5. 注解也可以不定义成员变量,但这样的注解没有什么卵用;
  6. 自定义注解需要使用元注解进行编写;

以水果与水果供应商为例:

水果名称注解 FruitName.java:

package com.grq.FruitAnnotation;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 水果名称注解
 */
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitName {
    String value() default "";
}

水果颜色注解 FruitColor.java:

package com.grq.FruitAnnotation;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 水果颜色注解
 */
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitColor {
    /**
     * 颜色枚举
     */
    public enum Color{ BLUE,RED,GREEN};

    /**
     * 颜色属性
     */
    Color fruitColor() default Color.GREEN;

}

水果供应者注解 FruitProvider.java:

package com.grq.FruitAnnotation;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 水果供应者注解
 */
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitProvider {
    /**
     * 供应商编号
     */
    public int id() default -1;

    /**
     * 供应商名称
     */
    public String name() default "";

    /**
     * 供应商地址
     */
    public String address() default "";
}

注解处理器 FruitInfoUtil.java:

package com.grq.FruitAnnotation;

import java.lang.reflect.Field;

/**
 * 注解处理器
 */
public class FruitInfoUtil {
    public static void getFruitInfo(Class<?> clazz){

        String strFruitName=" 水果名称:";
        String strFruitColor=" 水果颜色:";
        String strFruitProvicer="供应商信息:";

        Field[] fields = clazz.getDeclaredFields();

        for(Field field :fields){
            if(field.isAnnotationPresent(FruitName.class)){
                FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
                strFruitName=strFruitName+fruitName.value();
                System.out.println(strFruitName);
            }
            else if(field.isAnnotationPresent(FruitColor.class)){
                FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class);
                strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
                System.out.println(strFruitColor);
            }
            else if(field.isAnnotationPresent(FruitProvider.class)){
                FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class);
                strFruitProvicer=" 供应商编号:"+fruitProvider.id()+" 供应商名称:"+fruitProvider.name()+" 供应商地址:"+fruitProvider.address();
                System.out.println(strFruitProvicer);
            }
        }
    }
}

苹果 Apple.java:

package com.grq.FruitAnnotation;

/**
 * 注解使用
 */
public class Apple {

    @FruitName("Apple")
    private String appleName;

    @FruitColor(fruitColor = FruitColor.Color.RED)
    private String appleColor;

    @FruitProvider(id=1,name="陕西红富士集团",address="陕西省西安市延安路89号红富士大厦")
    private String appleProvider;

    public void setAppleColor(String appleColor) {
        this.appleColor = appleColor;
    }
    public String getAppleColor() {
        return appleColor;
    }

    public void setAppleName(String appleName) {
        this.appleName = appleName;
    }
    public String getAppleName() {
        return appleName;
    }

    public void setAppleProvider(String appleProvider) {
        this.appleProvider = appleProvider;
    }
    public String getAppleProvider() {
        return appleProvider;
    }

    public void displayName(){
        System.out.println("水果的名字是:苹果");
    }
}

测试输出水果信息 FruitTestAnnotation:

package com.grq.FruitAnnotation;

public class TestFruitAnnotation {
    public static void main(String[] args) {
        FruitInfoUtil.getFruitInfo(Apple.class);
    }
}

运行后的测试结果为:

水果名称:Apple 水果颜色:RED 供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延安路89号红富士大厦undefined

后记

这段时间虽然在 SpringMVC 中用注解用的飞起,各种 @RequestMapping, @Service, @Controller 等注解信手拈来,但还是不了解它的运作原理到底是什么样的。尤其是在框架中,大量运用到了注解与反射操作,所以以后也会认真了解一下如 Spring 框架中注解的运行原理,想必这无论是对理解框架,还是对理解注解本身,都会有很大的帮助。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年08月05日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java 注解 —— 注解的理解、注解的使用与自定义注解
  • 一. 注解基本介绍
    • 1.1 什么是注解?
      • 1.2 注解的作用
        • 1.3 注解的原理
        • 二. 元注解
          • 2.1 @Retention
            • 2.2 @Target
              • 2.3 @Documented
                • 2.4 @Inherited
                  • 2.5 @Repeatable
                  • 三. 注解的属性
                  • 四. 常用注解
                  • 五. 自定义注解
                  • 后记
                  相关产品与服务
                  容器服务
                  腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档