java注解

java 注解篇

什么是注解?

注释是给开发者看的,可以提升代码的可阅读性和可维护性,但是对于 java 编译器和虚拟机来说是没有意义的,编译后的字节码文件中没有任何注释信息。 而注解和注释有些类似,不过注解是给编译器和虚拟机看的。编译器和虚拟机可以在运行过程中获取注解信息,然后根据注解信息做各种想做的事。

注解如何使用?

  • 定义注解
  • 使用注解
  • 通过注解获取信息做各种想做的事

定义注解

下面定义了一个自定义注解:

public @interface Anno0 {
    String value() default "";
}

注解的定义需要注意:

  • 注解的定义和普通的类,接口一样,不过注解的关键字是 @interface
  • 注解的方法修饰符必须且只能为 public, 不能有其他的修饰符如 static,final,private 等。默认为 public。
  • 注解的方法类型只能为基本数据类型、枚举、Class、String、注解及以上类型的一维数组类型。
  • 方法名后的 ()不是定义参数的地方,也不能在里面定义任何参数。如 value()、name()。方法名表示注解的属性。
  • defult 表示默认值, 默认值得类型必须和前面定义的类型一样。如 value 属性的类型是 String, 默认值是空的字符串 ""。
  • 如果没有默认值,使用注解时,必须要为属性赋值。如果只有一个属性时,将属性名设为 value,使用时可以省略属性名。如下:
public @interface Anno1 {
String <span class="hljs-title function_">value</span><span class="hljs-params">()</span>;

String <span class="hljs-title function_">name</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;

}
public @interface Anno2 {

String <span class="hljs-title function_">name</span><span class="hljs-params">()</span>;

}
// 注解的使用
@Anno1(value = "value 属性没有默认值必须要传值")
public class AnnoTest {
}
@Anno1(value = "value 属性没有默认值必须要传值",name = "覆盖掉默认的 name 属性值")
public class AnnoTest {
}
@Anno0("不写赋值的属性名称时,默认给 value 赋值,必须保证有 value 这个属性")
public class AnnoTest {
}
@Anno2(name = "必须有属性名为 value() 时,才可以忽略不写属性名, 括号内直接赋值")
public class AnnoTest {
}

使用注解

使用 target 注解标注注解的使用范围:

@Target(ElementType.TYPE)
public @interface Anno0 {
String <span class="hljs-title function_">value</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;

}

ElementType 取值:

public enum ElementType {
    /** 类、接口(包括注解类型)、枚举类上 */
    TYPE,
<span class="hljs-comment">/** 字段(包括常量)上 */</span>
FIELD,

<span class="hljs-comment">/** 方法上 */</span>
METHOD,

<span class="hljs-comment">/** 方法参数上 */</span>
PARAMETER,

<span class="hljs-comment">/** 构造方法上 */</span>
CONSTRUCTOR,

<span class="hljs-comment">/** 本地变量上 */</span>
LOCAL_VARIABLE,

<span class="hljs-comment">/** 注解上 */</span>
ANNOTATION_TYPE,

<span class="hljs-comment">/** 包上 */</span>
PACKAGE,

<span class="hljs-comment">/**
 * 类型参数上
 *
 * <span class="hljs-doctag">@since</span> 1.8
 */</span>
TYPE_PARAMETER,

<span class="hljs-comment">/**
 * 使用到类型的任意语句中
 * <span class="hljs-doctag">@since</span> 1.8
 */</span>
TYPE_USE

}

使用 Retention 注解表示该注解的生效范围

@Retention(RetentionPolicy.RUNTIME)
public @interface Anno0 {
String <span class="hljs-title function_">value</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;

}

RetentionPolicy 的取值:

public enum RetentionPolicy {
    /**
     * 只在源码中生效,编译及运行时就丢失了,也就是 class 文件中就不存在了。
     */
    SOURCE,
<span class="hljs-comment">/**
 * 注解会被保留在编译后的class文件中,但是不会被保留在VM虚拟机在运行阶段
 */</span>
CLASS,

<span class="hljs-comment">/**
 * 注解在源码,class字节码以及运行阶段都存在
 * <span class="hljs-doctag">@see</span> java.lang.reflect.AnnotatedElement
 */</span>
RUNTIME

}

正常情况下,自定义注解上都要标注 target 和 retention 注解。

无参数

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Anno2 {
}
// 使用注解
@Anno2
public class AnnoTest {
}

一个参数的

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Anno0 {
String <span class="hljs-title function_">value</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;

}
@Anno0("属性名为 value 可以省略")
public class AnnoTest {
}

多个参数

public @interface Anno1 {
String <span class="hljs-title function_">value</span><span class="hljs-params">()</span>;

String <span class="hljs-title function_">name</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;

}

@Anno1(value = "属性名为 value 可以省略",name = "多个参数注解要指定属性名")
public class AnnoTest {
}

数组类型参数

public @interface Anno3 {
String[] name() <span class="hljs-keyword">default</span> {};

}

@Anno3(name = {"数组类型", "以逗号分隔", "大括号 {} 包裹"})
public class AnnoTest {
}
// 当只有一个值时可以省略大括号
@Anno3(name= "数组类型")
public class AnnoTest {
}
// 当属性名为 value 时,可以省略
@Anno3({"数组类型", "属性值为 value 省略"})
public class AnnoTest {
}
// 只有一个值
@Anno3("数组类型")
public class AnnoTest {
}

不同 ElementType 的用法

// Type 类型可以用在类,接口,注解,枚举类型上
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Anno4 {
String <span class="hljs-title function_">name</span><span class="hljs-params">()</span>;

}
@Anno4(name = "用在注解上")
public @interface AnnoTest{}
@Anno4(name = "用在类上")
class TypeTest{}
@Anno4(name = "用在接口上")
interface InterfaceTest{}
@Anno4(name = "用在枚举上")
enum EnumTest{}

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, 
        ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Anno5 {
    String name();
}
public class AnnotationTest {

    @Anno5(name = "作用在字段上")
    private String name;
    
    @Anno5(name = "作用在构造方法上")
    AnnotationTest(){}

    @Anno5(name = "作用在方法上")
    void fun1(@Anno5(name = "作用在参数上") String param) {
        
        @Anno5(name = "作用在本地变量") 
        int i = 1;
    }
}

jdk8 新增了 TYPE_PARAMETER 和 TYPE_USE 类型

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Anno6 {
String <span class="hljs-title function_">value</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;

}

abstract class Test<@Anno6("作用在泛型参数上 =TYPE_PARAMETER") K,V>{

<span class="hljs-keyword">abstract</span> K <span class="hljs-title function_">fun1</span><span class="hljs-params">(V v)</span>;

}
class Test1{
public static void main(String[] args) {

    Test&lt;<span class="hljs-meta">@Anno6("作用在类型参数上=TYPE_USE")</span>String, Integer&gt; test = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Test</span>&lt;String, Integer&gt;() {
        <span class="hljs-meta">@Override</span>
        <span class="hljs-meta">@Anno6("作用在类型参数上=TYPE_USE")</span> String <span class="hljs-title function_">fun1</span><span class="hljs-params">( <span class="hljs-meta">@Anno6("作用在类型参数上=TYPE_USE")</span>Integer integer)</span> {
            <span class="hljs-keyword">return</span> String.valueOf(integer);
        }
    };
}

}

注解信息的获取

为了运行时能准确获取注解的信息,Java 在 java.lang.reflect 反射包下新增了 AnnotatedElement 接口,他主要用于表示目前正在虚拟机中运行的程序中已 使用注解的元素,通过该接口提供的方法可以利用反射技术读取注解信息。 AnnotatedElement

  • Package: 用来表示包信息
  • Class: 用来表示类信息
  • Constructor: 用来表示构造方法信息
  • Field: 用来表示类中的属性信息
  • Method: 用来表示方法信息
  • Parameter: 用来表示方法参数信息
  • TypeVariable: 用来表示类型变量信息, 如类上定义的泛型类型变量,方法上定义的泛型变量

AnnonatedElement 常用方法

返回值方法名称说明
A extends Annotation getAnnotation(Class AnnotationClass) 该元素如果存在指定类型的注解,则返回该注解,否则返回 null
Annotation[] getAnnotations() 返回此元素上的所有注解,包括从父类上继承的
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 如果指定类型的注解存在此元素上,则返回 true,否则 false
Annotation[] getDeclaredAnnotations() 返回直接存在此元素上的所有注解,不包括父类继承的注解。

使用案例:

public class AnnotationUseTest {
    public static void main(String[] args) {
    <span class="hljs-type">Anno4</span> <span class="hljs-variable">anno4</span> <span class="hljs-operator">=</span> A.class.getAnnotation(Anno4.class);
    System.out.println(<span class="hljs-string">"获取类A上的Anno4注解信息:"</span> + anno4.name());
    Annotation[] annotations = A.class.getAnnotations();
    System.out.println(<span class="hljs-string">"获取类A上的所有注解:"</span>);
    <span class="hljs-keyword">for</span> (Annotation annotation : annotations) {
        System.out.println(<span class="hljs-string">"类A上的注解:"</span> + annotation);
    }
    <span class="hljs-type">Anno4</span> <span class="hljs-variable">annotation</span> <span class="hljs-operator">=</span> AnnoTest.class.getAnnotation(Anno4.class);
    System.out.println(<span class="hljs-string">"获取AnnoTest注解上的Anno4注解的信息:"</span> + annotation);

    <span class="hljs-type">Field</span> <span class="hljs-variable">desc</span> <span class="hljs-operator">=</span> ReflectionUtils.findField(A.class, <span class="hljs-string">"desc"</span>);
    Optional.ofNullable(desc).ifPresent(a -&gt; {
        System.out.println(<span class="hljs-string">"字段上的注解:"</span> + a.getAnnotation(Anno5.class));
        System.out.println(a.getAnnotatedType().getAnnotation(Anno6.class));
    });

    Constructor&lt;?&gt; constructor = A.class.getDeclaredConstructors()[<span class="hljs-number">0</span>];
    <span class="hljs-type">Anno5</span> <span class="hljs-variable">annotation1</span> <span class="hljs-operator">=</span> constructor.getAnnotation(Anno5.class);
    System.out.println(<span class="hljs-string">"构造方法上的注解:"</span> + annotation1);

    <span class="hljs-type">Method</span> <span class="hljs-variable">setDesc</span> <span class="hljs-operator">=</span> ReflectionUtils.findMethod(A.class, <span class="hljs-string">"setDesc"</span>, String.class);
    Optional&lt;Method&gt; method = Optional.ofNullable(setDesc);
    method.ifPresent(f -&gt; System.out.println(<span class="hljs-string">"方法上的注解:"</span> + f.getAnnotation(Anno5.class)));
    <span class="hljs-type">Annotation</span> <span class="hljs-variable">annotation2</span> <span class="hljs-operator">=</span> method.map(f -&gt; f.getParameters()[<span class="hljs-number">0</span>]).map(f -&gt; f.getAnnotations()[<span class="hljs-number">0</span>]).get();
    System.out.println(<span class="hljs-string">"作用在方法参数上:"</span> + annotation2);

    <span class="hljs-type">Anno6</span> <span class="hljs-variable">annotation3</span> <span class="hljs-operator">=</span> B.class.getTypeParameters()[<span class="hljs-number">0</span>].getAnnotation(Anno6.class);
    System.out.println(<span class="hljs-string">"类型参数上的注解--TYPE_PARAMETER:"</span> + annotation3);

    <span class="hljs-type">Method</span> <span class="hljs-variable">test</span> <span class="hljs-operator">=</span> ReflectionUtils.findMethod(A.class, <span class="hljs-string">"test"</span>);
    Optional.ofNullable(test).ifPresent(f -&gt;
            System.out.println(f.getAnnotatedReturnType().getAnnotation(Anno6.class))
    );

}

}

@Anno4(name = "用在类上的注解")
class A {

<span class="hljs-meta">@Anno5(name = "用在类属性上")</span>
<span class="hljs-keyword">private</span> <span class="hljs-meta">@Anno6("用在成员变量类型上--TYPE_USE")</span> String desc;

<span class="hljs-meta">@Anno5(name = "用在构造方法上")</span>
A() {
}

<span class="hljs-meta">@Anno5(name = "用在方法上")</span>
<span class="hljs-keyword">void</span> <span class="hljs-title function_">setDesc</span><span class="hljs-params">(<span class="hljs-meta">@Anno5(name = "用在方法参数上")</span> String desc)</span> {
    <span class="hljs-meta">@Anno5(name = "用在本地变量上")</span> <span class="hljs-type">Integer</span> <span class="hljs-variable">aa</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;

    <span class="hljs-built_in">this</span>.desc = desc;
}

<span class="hljs-meta">@Anno6("用在返回类型上")</span> String <span class="hljs-title function_">test</span><span class="hljs-params">()</span> {
    <span class="hljs-meta">@Anno6("用在局部变量类型上--TYPE_USE")</span> <span class="hljs-type">String</span> <span class="hljs-variable">name</span> <span class="hljs-operator">=</span> <span class="hljs-string">"aaa"</span>;
    <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>;
}

}

interface B<@Anno6("作用在类型参数上") String>{

}

@Inherit 类之间注解的继承

首先看这个注解的源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

根据 ElementType 的值可以知道这个注解是专门作用在注解上的。
作用:让子类可以继承父类中被 @Inherited 修饰的注解。注意是继承父类的,如果接口上的注解也用 @Inherited 修饰了,那么接口的实现类无法继承这个注解 首先我们看不加这个注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Anno7 {
String[] notes() <span class="hljs-keyword">default</span> {};

String <span class="hljs-title function_">desc</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;

}

自定义一个注解。首先来看不加这个注解的效果

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Anno7 {
String[] notes() <span class="hljs-keyword">default</span> {};

String <span class="hljs-title function_">desc</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;

}

@Anno7(desc = "不加 Inherited")
abstract class AbstractAnno7{

}
public class Anno7Test extends AbstractAnno7 {

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
        Annotation[] annotations = Anno7Test.class.getAnnotations();
        <span class="hljs-comment">// annotations数组为空数组,因此无结果打印</span>
        <span class="hljs-keyword">for</span> (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }

}

可以看到我们获取不到父类的注解信息。下面在试试加上这个注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Anno7 {
String[] notes() <span class="hljs-keyword">default</span> {};

String <span class="hljs-title function_">desc</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;

}

再次执行上面的 main 方法:

@Anno7(desc = "加 Inherited")
abstract class AbstractAnno7{

}
public class Anno7Test extends AbstractAnno7 {

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
    Annotation[] annotations = Anno7Test.class.getAnnotations();
    <span class="hljs-keyword">for</span> (Annotation annotation : annotations) {
        System.out.println(annotation);<span class="hljs-comment">//@com.xiazhi.annocation.Anno7(desc=加Inherited, notes=[])</span>
    }
}

}

再试下在接口上使用该注解:

@Anno7(desc = "使用在接口上")
interface IAnno7{}
public class Anno7Test implements IAnno7 {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
    Annotation[] annotations = Anno7Test.class.getAnnotations();
    <span class="hljs-comment">// 无结果</span>
    <span class="hljs-keyword">for</span> (Annotation annotation : annotations) {
        System.out.println(annotation);
    }
}

}

Repeatable 重复使用注解

定义注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Anno8 {
}

看下面一段代码:

@Anno8
@Anno8 // 这里会报错
public class Anno8Test {
}

默认情况下,注解是不允许重复使用的,如果我们想像上面一样重复使用注解,需要添加 Repeatable 注解 看一下 Repeatable 注解:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation type</em> for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}

注意 value 是没有默认值的,因此使用时要对 value 属性赋值。值是一个容器注解。容器注解的定义要求如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Anno8s {
Anno8[] value();

}

容器注解必须有一个 value 参数,参数类型为子注解类型的数组。 使用 Repeatable 注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Anno8s.class)
public @interface Anno8 {
}

@Anno8
@Anno8
public class Anno8Test {
}

此时使用重复注解就不会报错了。

spring 对于注解的增强

要先导入 spring-core 的依赖。 首先我们来看一个问题:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface A1{
    String value() default "a1";
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@A1
@interface B1{
String value() default "b1";
}

@B1("hello")
public class DemoTest {

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
    System.out.println(AnnotatedElementUtils.getMergedAnnotation(DemoTest.class, B1.class));
    System.out.println(AnnotatedElementUtils.getMergedAnnotation(DemoTest.class,A1.class));
}

}

AnnotatedElementUtils 是 spring 封装的工具类。此时有个问题,如果我们像要在给 B1 赋值的时候同时给 B1 上的 A1 设置值,是没有办法的,这是由于注解定义 无法继承导致的。Spring 通过 Aliasfor 注解解决了这个问题。 通过 Aliasfor 解决上面的问题:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface A1{
    String value() default "a1";
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@A1
@interface B1{

String <span class="hljs-title function_">value</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">"b1"</span>;

<span class="hljs-meta">@AliasFor(value = "value",annotation = A1.class)</span>
String <span class="hljs-title function_">a1Value</span><span class="hljs-params">()</span>;

}

@B1(value = "hello", a1Value = "给 A1 的 value 赋值")
public class DemoTest {

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
    System.out.println(AnnotatedElementUtils.getMergedAnnotation(DemoTest.class, B1.class));
    System.out.println(AnnotatedElementUtils.getMergedAnnotation(DemoTest.class, A1.class));
}

}

运行结果:

@com.xiazhi.annocation.B1(value=hello, a1Value=给A1的value赋值)
@com.xiazhi.annocation.A1(value=给A1的value赋值)

我们看一下使用 Aliasfor 的地方的代码:

@AliasFor(value = "value",annotation = A1.class)
String a1Value();

annatation 表示要给哪儿个注解赋值,value 表示注解的方法,本例中我们要将 a1Value() 的值赋值给 A1 注解的 value 方法。

Aliasfor 注解

看一下 Aliasfor 注解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface AliasFor {
    @AliasFor("attribute")
    String value() default "";
<span class="hljs-meta">@AliasFor("value")</span>
String <span class="hljs-title function_">attribute</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;

Class&lt;? <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Annotation</span>&gt; annotation() <span class="hljs-keyword">default</span> Annotation.class;

}

在 value 和 attribute 方法上使用了 Aliasfor 注解,说明给 value 赋值时同时就会给 attribute 赋值,给 attribute 赋值时同时会给 value 赋值

在同一个注解中使用 Aliasfor
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface An{
<span class="hljs-meta">@AliasFor("value")</span>
String <span class="hljs-title function_">name</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;

<span class="hljs-meta">@AliasFor("name")</span> <span class="hljs-comment">//如果不指定annotation属性,则默认是当前注解,如果不指定value和attribute,则自动将修饰的参数作为value和attribute的值</span>
String <span class="hljs-title function_">value</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;

}

@An(value = "hello")// 同一个注解内设置 Aliasfor 注解时,如果给两个属性都赋值会报错
public class DemoTest2 {

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
    <span class="hljs-type">An</span> <span class="hljs-variable">mergedAnnotation</span> <span class="hljs-operator">=</span> AnnotatedElementUtils.getMergedAnnotation(DemoTest2.class, An.class);
    System.out.println(mergedAnnotation);
}

}