目录
概述
目前大部分框架(如Spring)都使用了注解简化代码并提高编码的效率,因此掌握并深入理解注解对于一个Java工程师是来说是很有必要的事。看起来基本上的框架都用了注解,springboot里面的注解更是多,不理解注解真的是框架时代寸步难行。
还有人说框架就是注解+设计模式+反射,对于我这个小白来讲,赶紧理解注解才是正道。只为提高自己,脱离增删改查工程师,尽快转型自带光环的programmer。
解释
官方
Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。
官方的定义,很难理解,为了解释注解这个概念有引入了元数据的概念,这就是找事了。
民间
我们民间的解释就容易多了
这里我参考了很多的博客,这几篇文章写的很详细,我在此基础上做自己的总结
秒懂,Java 注解 (Annotation)你可以这样学
深入理解Java注解类型(@Annotation)
java中的注解,使用、面试再也不用愁了
理解
这个比方很简单容易理解,注解就是标签,通常我们给人贴上学霸、学渣的标签,其实这就是帮助别人快速理解这个东西了。标签是对事物行为的某些角度的评价与解释。
① jdk 5.0 新增的功能
② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,程序员可以在不改变原逻辑的情况下, 在源文件中嵌入一些补充信息。
③在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
框架 = 注解 + 反射机制 + 设计模式 这句话好重要,我就是冲着这句话来写这篇文章的,为以后的理解框架做好基础,拒绝只做增删改查的coder,励志成为programmer
语法
注解的基本语法就是这样,用 @interface来标识这是一个注解,注解和class、interface是同一个等级的,不要认为注解不重要。
public @interface MyAnnotation {
String value() ;
}
我们使用了@interface声明了MyAnnotation 注解,从代码上看注解的定义很像接口的定义,确实如此,毕竟在编译后也会生成Test.MyAnnotation 文件
元注解
这玩意他娘的有意思了,没听过的真不好理解这东西,元注解是标识注解的注解,字面意思就是给标签加的标签,元注解也是一张标签,但是它是一张特殊的标签,它的作用和目的就是给其他普通的标签进行解释说明的。
元注解有五种分别是: @Retention、@Documented、@Target、@Inherited、@Repeatable 。
@Retention
/**
* Indicates how long annotations with the annotated type are to
* be retained. If no Retention annotation is present on
* an annotation type declaration, the retention policy defaults to
* {@code RetentionPolicy.CLASS}.
*
* <p>A Retention meta-annotation has effect only if the
* meta-annotated type is used directly for annotation. It has no
* effect if the meta-annotated type is used as a member type in
* another annotation type.
*
* @author Joshua Bloch
* @since 1.5
* @jls 9.6.3.2 @Retention
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
看源码人家是怎么写的,也是声明了一个@interface,名字叫Retention的注解,他里面需要一个RetentionPolicy这个类型的value();也就是使用的时候需要指明RetentionPolicy这个枚举使用的哪一个,或者有默认的值,这个时候再去看看RetentionPolicy的源码
/*
* Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
package java.lang.annotation;
/**
* Annotation retention policy. The constants of this enumerated type
* describe the various policies for retaining annotations. They are used
* in conjunction with the {@link Retention} meta-annotation type to specify
* how long annotations are to be retained.
*
* @author Joshua Bloch
* @since 1.5
*/
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
它的取值如下:
RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视
RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
@Documented
看源码
/**
* Indicates that annotations with a type are to be documented by javadoc
* and similar tools by default. This type should be used to annotate the
* declarations of types whose annotations affect the use of annotated
* elements by their clients. If a type declaration is annotated with
* Documented, its annotations become part of the public API
* of the annotated elements.
*
* @author Joshua Bloch
* @since 1.5
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
人家注释说的多清楚它的作用是能够将注解中的元素包含到 Javadoc 中去。
@Target
看源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
ElementType[] value();这里这个玩意是什么意思呢,他是说你使用这个注解的时候可以指定值是ElementType这个类型的数组,elementType这个的源码就不贴了,ElementType他本身也是一个枚举enum,解释一下
@Target 指定了注解运用的地方。
你可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。
@Target 有下面的取值
ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
说的比较抽象。代码来解释。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}
注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。
可以这样理解:
老子非常有钱,所以人们给他贴了一张标签叫做富豪。
老子的儿子长大后,只要没有和老子断绝父子关系,虽然别人没有给他贴标签,但是他自然也是富豪。
老子的孙子长大了,自然也是富豪。
这就是人们口中戏称的富一代,富二代,富三代。虽然叫法不同,好像好多个标签,但其实事情的本质也就是他们有一张共同的标签,也就是老子身上的那张富豪的标签。
@Repeatable 。
Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。
什么样的注解会多次应用呢?通常是注解的值可以同时取多个。
举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。这个代码是抄的。。。
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@interface Person{
String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}
@interface Persons {
Person[] value();
}
这个解释太好了
按照规定,它里面必须要有一个 value 的属性,属性类型是一个被@Repeatable 注解过的注解数组,注意它是数组。
如果不好理解的话,可以这样理解。Persons 是一张总的标签,上面贴满了 Person 这种同类型但内容不一样的标签。把 Persons 给一个 SuperMan 贴上,相当于同时给他贴了程序员、产品经理、画家的标签。
我们可能对于 @Person(role=“PM”) 括号里面的内容感兴趣,它其实就是给 Person 这个注解的 role 属性赋值为 PM ,大家不明白正常,马上就讲到注解的属性这一块。
预置注解
java预置的注解其实还是比较多的,但是我们只要挑出几个比较重要的就好了。
@Deprecated
这个注解是用来标记过时的元素,编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。
@Override
这个注解再熟悉不过了,提示该方法是接口方法的实现或者是子类重写的父类的方法。
@SuppressWarnings
阻止警告的意思,上面说过调用被@Deprecated注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过@SuppressWarnings达到目的。这个注解具体怎么执行的就是编译器的问题了,关于这个注解是怎么执行的都是反射的问题,java中用起来也是反射,spring中用起来也是反射,springboot中的注解更多。理解这个注解很重要。
@SafeVarargs
参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生unchecked这样的警告,它是在Java 1.7的版本中加入的。
JDK8中注解的新特性:可重复注解、类型注解
可重复注解:
① 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
② MyAnnotation的Target和Retention等元注解与MyAnnotations相同。
类型注解:
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明。ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
注解的属性
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id();
String msg();
}
上面代码定义了 TestAnnotation 这个注解中拥有 id 和 msg 两个属性。在使用的时候,我们应该给它们进行赋值。
赋值的方式是在注解的括号内以 value="" 形式,多个属性之前用 ,隔开。
@TestAnnotation(id=3,msg="hello annotation")
public class Test {
}
需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。
注解中属性可以有默认值,默认值需要用 default 关键值指定。比如:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default -1;
public String msg() default "Hi";
}
TestAnnotation 中 id 属性默认值为 -1,msg 属性默认值为 Hi。它可以这样应用。
@TestAnnotation()
public class Test {}
因为有默认值,所以无需要再在 @TestAnnotation 后面的括号里面进行赋值了,这一步可以省略。
另外,还有一种情况。如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。
public @interface Check {
String value();
}
上面代码中,Check 这个注解只有 value 这个属性。所以可以这样应用。
@Check("hi")
int a;
这和下面的效果是一样的
@Check(value="hi")
int a;
最后,还需要注意的一种情况是一个注解没有任何属性。比如
public @interface Perform {}
那么在应用这个注解的时候,括号都可以省略。
@Perform
public void testMethod(){}
如果注解有成员,在使用注解时,需要指明成员的值。
为自定义注解加深理解
自定义注解必须配上注解的信息处理流程(使用反射)才意义。
自定义注解通过都会指明两个元注解:Retention、Target
注解怎么用
获取注解
也就是我们通过反射获取类 、函数或成员上的运行时注解信息,从而实现动态控制程序运行的逻辑。举个例子,看看我们如何通过反射来控制程序运行的逻辑。反射可以让我们在运行时获取类的属性,方法,构造方法、父类、接口等信息,通过反射还可以让我们在运行期实例化对象、调用方法、即使方法或属性是私有的的也可以通过反射的形式调用。反射的原理就是读取编译后的class文件,啥都有了,读出来还不容易么,不管你怎么写的,私有的或者什么的方法,我都能读到。
注解使用
import java.lang.annotation.*;
/**
* @Description 自定义一个注解 修饰属性
* @Author WangWenpeng
* @Date 9:38 2020/4/15
* @Param
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.FIELD)
public @interface Fields {
int sort() default 0;
String value();
}
/**
* @Description 修饰类
* @Author WangWenpeng
* @Date 10:54 2020/4/15
* @Param
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface ConsAnnotation {
String[] request();//表明可以声明多个string
}
/**
* @Description 给类和属性加注解
* @Author WangWenpeng
* @Date 10:54 2020/4/15
* @Param
*/
@ConsAnnotation(request = {"java", "小鸟程序员"})
class User {
@Fields("张三")
private String userName;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
获取这些注解
package com.atguigu.java1;
import java.lang.reflect.Field;
import java.util.Arrays;
/**
* @ClassName ValueTest
* @Description: annotationtest
* @Author: WangWenpeng
* @date: 10:39 2020/4/15
* @Version 1.0
*/
public class ValueTest {
public static void main(String[] args) throws Exception {
User user = new User();
// 1、 获取 User类上的注解 @ConsAnnotation
ConsAnnotation anno = user.getClass().getAnnotation(ConsAnnotation.class);
String[] arr = anno.request();
System.out.println(Arrays.toString(arr));
// 2、 获取User类中 private String userName; 变量上的注解 @Field
Field f = user.getClass().getDeclaredField("userName");
Fields anno2 = f.getAnnotation(Fields.class);
user.setUserName(anno2.value());
System.out.println(user.getUserName());
}
}
看输出
总结
提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
注解就是一个标签,你也可以当成一个便利贴,在哪使用就看你是否需要这个便利贴了。
公众号,关注一下