今天看啥  ›  专栏  ›  defaultCoder

简述java中的注解

defaultCoder  · 简书  ·  · 2019-06-16 23:49

文章预览

本周任务中触及到自定义注解的使用, 于是就专门学习了一下注解.

学习注解有什么好处?

spring, mybatis等框架中, 大量使用了注解, 想要更易于理解框架, 需要我们对注解有一定的了解.

什么是注解?

注解是JDK5开始引入的概念, 他可以将任何的信息或者元数据(元数据即: 描述数据的数据)与程序的元素(类, 方法, 成员变量等)进行关联. 为这些程序的元素加上直观明了的说明. 这些与业务逻辑是无关的, 大家可以理解为注解就是为这些元素(类, 方法, 成员变量等)贴一个标签.
注解可以用在包, 类, 构造方法, 方法, 成员变量, 参数以及,本地变量的声明语句中.

java中常见的标准注解

注解我们无处不见, 大多为自定义注解, 存在于框架中, 实际上我们也常常见到在JDK中的几个自带的注解:@Override, @Deprecated, @SuppressWarnings
这三个注解大家一定也不陌生.

  • @Override表示覆盖或者重写父类的方法
  • @Deprecated表示该方法已过时.
  • @SuppressWarnings表示忽略指定警告.
    使用方式演示:
public class JdkAnnotation {
    // 使用该注解可以忽略unused警告
    @SuppressWarnings("unused")
    public static void main(String[] args) {
        // 如果没有使用该变量, 编译器会有警告: The value of the variable string is not used
        String string = "unused";
    }
    
    // 该注解标志该方法已过时
    @Deprecated
    public void myDeprecatedMethod(){
                // jdk9中的finalized()已被标注为过时方法
    }
    
    // 重写父类方法
    @Override
    public String toString() {
        return super.toString();
    }
}

以上就是JDK自带的标准注解.其实除了这三个意外, 还有JDK7中加入的@SafeVarargsJDK8中加入的@FunctionalInterface, 大家感兴趣的话可以查阅一下相关资料深入了解.

自定义注解

我们经常使用的例如spring中的注解, 都是spring框架中自定义的注解. 我们需要了解它的话需要了解什么是自定注解, 如何自定义? 我们先看看自定义注解是什么样子的, 再进一步说明.


自定义注解例子
元注解

看了图片或许大家不明白其中的元注解一词, 我们先了解一下元注解是什么.
看到带一个元字, 不难想到和元数据类似, 元数据是描述数据的数据, 而元注解当然就是描述注解的注解.
这样解释可能不太好理解, 简单来说可以说是, 再自定义注解的时候, 我们需要把这个注解的生命周期, 作用域给设置, 这些就是使用元注解完成的.

大概了解了一下元注解的意思, 那么我在上图例子中, 使用的三个元注解分别代表什么意思, 接下来逐一介绍一下.

  1. @Target是用于描述该自定义注解的作用域: 表示该注解用于哪部分, 比如我上图中的例子使用ElementType.TYPE标注该自定义注解用于类上.如果作用域类型有多个用英文逗号分隔
    其具体参数列表如下:
  • ElementType.CONSTRUCTOR: 用于描述构造器
  • ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
  • ElementType.LOCAL_VARIABLE: 用于描述局部变量
  • ElementType.METHOD: 用于描述方法
  • ElementType.PACKAGE: 用于描述包
  • ElementType.PARAMETER: 用于描述参数
  • ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明
  1. @Retention是用于定义该注解的生命周期, 比如我上图中使用了RetentionPolicy.RUNTIME是指运行期也保留的注解. 这样可以通过反射获取到该自定义注解信息.
    其具体参数列表如下:
  • RetentionPolicy.SOURCE : 在编译阶段丢弃. 这些注解在编译结束之后就不再有任何意义, 所以它们不会写入字节码. @Override, @SuppressWarnings, @Deprecated都属于这类注解.
  • RetentionPolicy.CLASS : 在类加载的时候丢弃. 在字节码文件的处理中有用. 注解默认使用这种方式
  • RetentionPolicy.RUNTIME : 始终不会丢弃, 运行期也保留该注解, 因此可以使用反射机制读取该注解的信息. 我们自定义的注解通常使用这种方式.
  1. @Documented 这是一个标记该注解信息会添加到java文档中

除了上述我例子中使用到三个元注解以外,还有两个元注解:

  1. @Inherited是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的. 如果一个使用了@Inherited 修饰的annotation 类型被用于一个class, 则这个annotation 将被用于该class 的子类.
    如果读不懂这个概念, 这里我特地说明一下: 这里的继承意思不是使用这个元注解的自定义注解的本身可以被继承, 而是如果一个超类被@Inherited注解过的自定义注解进行注解的话, 那么它的字类如果没有被任何注解应用的话, 那么这个子类就会继承这个超类的注解.
    或许还是说得比较抽象, 我们看看代码:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}

@Test
public class A {}

public class B extends A {}

自定义注解Test, 被@Inherited修饰, 之后类ATest注解, 类B继承了类A也拥有Test这个注解.
用互联网中流传的一个形象例子再描述一下一定可以明白:

老子非常有钱,所以人们给他贴了一张标签叫做富豪。

老子的儿子长大后,只要没有和老子断绝父子关系,虽然别人没有给他贴标签,但是他自然也是富豪。

老子的孙子长大了,自然也是富豪。

这就是人们口中戏称的富一代,富二代,富三代。虽然叫法不同,好像好多个标签,但其实事情的本质也就是他们有一张共同的标签,也就是老子身上的那张富豪的标签。
  1. @Repeatable当然就是可重复的意思, 这个是JDK1.8中引进的新元注解.
    什么样的注解会被多次重复应用呢? 不难想到又时候需要值取多个的时候.
    比如一个人担任多个角色, 比如我的总监不仅是一个程序员, 还得兼任UI.
    我们还是看看代码:
@interface Persons {
    Person[]  value();
}

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

@Person(role="coder")
@Person(role="UI")
public class manager{
    
}

大家可以看到上面代码中, @Repeatable注解了Person. 而 @Repeatable后面括号中的类相当于一个容器注解.

什么是容器注解呢?就是用来存放其它注解的地方. 它本身也是一个注解.
我们再看看代码中的相关容器注解:

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

按照规定, 它里面必须要有一个 value 的属性, 属性类型是一个被 @Repeatable 注解过的注解数组, 注意它是数组.

注解的属性

我们总能在例子中看到型如: String value(), Person[] value()这样的代码段, 这些就是注解的属性, 也叫做成员变量. 看到括号, 或许大家第一时间想到了方法, 但注解只有成员变量, 没有方法. 我们还是看看使用方式.
定义注解的代码:

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

使用注解的代码:

@TaskDesc(value="第一个定时任务", status="Y")
public class TaskOne {
    
}

而由于status是有默认值的,所以我们可以不必对其进行值的定义, 使用默认的值也可以, 这样的话我们只需要定义value就可以了. 而在注解中有这么一个特殊规定, 如果只需要使用value的话, 应用这个注解时可以直接接属性值填写到括号内, 比如这样:

@TaskDesc("第一个定时任务")
public class TaskOne {
    
}

这样的话, 如果进行取值的话, 通过value可以得到"第一个定时任务",通过status可以得到默认值"N".

该特性体现在多处, 我们可以看到例如springMVCrequestMapping在使用methodGET请求时候, 可以只这样写requestMapping("/xxx"), 但是如果需要修改请求方式为POST则需要写成:requestMapping(value="/xxx", method = RequestMethod.POST)

注解的提取

我们已经讨论了自定义注解的生命与使用了, 那么我们也知道仅仅将这些注解使用到我们想贴的位置, 对我们任何帮助都没有, 它仅仅是标签而已, 我们需要对拥有该标签的内容进行识别, 提取, 使用.
那么如何提取呢?
我们可以通过反射来进行这一系列的操作.

注解与反射.

注解可以通过反射来获取.

  1. 我们可以先通过isAnnotationPresent()方法判断它是否应用了某个注解.
  2. 然后通过getAnnotation()来获取Annotation对象
  3. 获得到Annotation对象后, 我们可以调用它的属性来获取值.
    这里我们看看代码:
    自定义注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
    String value();
    String msg() default "i am msg";
}

应用该注解的类:

@TestAnnotation("2333")
public class Test {

}

提取注解信息:

public class Application {
    public static void main(String[] args) {
        boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
        if (hasAnnotation) {
            TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
            System.out.println("value:" + testAnnotation.value());
            System.out.println("msg:" + testAnnotation.msg());
        }
    }
}

控制台打印结果

value:2333
msg:i am msg

注解到底用在哪?

其实注解的定义已经告诉过我们了, 它不会影响代码, 只是某些内容的标签, 我们可以利用这些标签来区别一些内容, 来做一些处理, 比如我刚刚的例子中, 如果提取到的value值等于2333时, 我们做某种操作, 而值为其他时, 我们又做其他操作. 这样的场景.
还是看不明白的话, 还是可以说说上文提到的springMVCrequestMapping, 当我们传的methodGET时候, 是对应解析GET请求, 而如果是POST时, 解析的是POST请求. 这样的例子.
其实我们能在框架看到注解的地方, 就是它使用场景了.

结束语

今天对注解的讨论就到这儿了, 希望大家有所收获, 如果文中有所纰漏, 请大家指正, 感谢阅读.

………………………………

原文地址:访问原文地址
快照地址: 访问文章快照
总结与预览地址:访问总结与预览