前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring系列第十七讲 深入理解Java注解及Spring对注解的增强(上)

Spring系列第十七讲 深入理解Java注解及Spring对注解的增强(上)

作者头像
易兮科技
发布2020-11-24 15:41:46
1.2K0
发布2020-11-24 15:41:46
举报
文章被收录于专栏:CSDN博客专栏

Spring系列第十七讲

什么是注解?

代码中注释大家都熟悉吧,注释是给开发者看的,可以提升代码的可读性和可维护性,但是对于java编译器和虚拟机来说是没有意义的,编译之后的字节码文件中是没有注释信息的;而注解和注释有点类似,唯一的区别就是注释是给人看的,而注解是给编译器和虚拟机看的,编译器和虚拟机在运行的过程中可以获取注解信息,然后可以根据这些注解的信息做各种想做的事情。比如:大家对@Override应该比较熟悉,就是一个注解,加在方法上,标注当前方法重写了父类的方法,当编译器编译代码的时候,会对@Override标注的方法进行验证,验证其父类中是否也有同样签名的方法,否则报错,通过这个注解是不是增强了代码的安全性。

总的来说:注解是对代码的一种增强,可以在代码编译或者程序运行期间获取注解的信息,然后根据这些信息做各种牛逼的事情。

注解如何使用?

3个步骤:

  • 定义注解
  • 使用注解
  • 获取注解信息做各种牛逼的事情

定义注解

定义注解语法

jdk中注解相关的类和接口都定义在java.lang.annotation包中。

注解的定义和我们常见的类、接口类似,只是注解使用@interface来定义,如下定义一个名称为MyAnnotation的注解:

代码语言:javascript
复制
public @interface MyAnnotation {
}

注解中定义参数

注解有没有参数都可以,定义参数如下:

代码语言:javascript
复制
public @interface 注解名称{
    [public] 参数类型 参数名称1() [default 参数默认值];
    [public] 参数类型 参数名称2() [default 参数默认值];
    [public] 参数类型 参数名称n() [default 参数默认值];
}

注解中可以定义多个参数,参数的定义有以下特点:

  1. 访问修饰符必须为public,不写默认为public
  2. 该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组
  3. 该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作)
  4. 参数名称后面的()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法
  5. default代表默认值,值必须和第2点定义的类型一致
  6. 如果没有默认值,代表后续使用注解时必须给该类型元素赋值

指定注解的使用范围:@Target

使用@Target注解定义注解的使用范围,如下:

代码语言:javascript
复制
@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface MyAnnotation {
}

上面指定了MyAnnotation注解可以用在类、接口、注解类型、枚举类型以及方法上面,自定义注解上也可以不使用@Target注解,如果不使用,表示自定义注解可以用在任何地方。

看一下@Target源码:

代码语言:javascript
复制
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

有一个参数value,是ElementType类型的一个数组,再来看一下ElementType,是个枚举,源码如下:

代码语言:javascript
复制
package java.lang.annotation;
/*注解的使用范围*/
public enum ElementType {
       /*类、接口、枚举、注解上面*/
    TYPE,
    /*字段上*/
    FIELD,
    /*方法上*/
    METHOD,
    /*方法的参数上*/
    PARAMETER,
    /*构造函数上*/
    CONSTRUCTOR,
    /*本地变量上*/
    LOCAL_VARIABLE,
    /*注解上*/
    ANNOTATION_TYPE,
    /*包上*/
    PACKAGE,
    /*类型参数上*/
    TYPE_PARAMETER,
    /*类型名称上*/
    TYPE_USE
}

指定注解的保留策略:@Retention

我们先来看一下java程序的3个过程

源码阶段

源码被编译为字节码之后变成class文件

字节码被虚拟机加载然后运行

那么自定义注解会保留在上面哪个阶段呢?可以通过@Retention注解来指定,如:

代码语言:javascript
复制
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
}

上面指定了MyAnnotation只存在于源码阶段,后面的2个阶段都会丢失。

来看一下@Retention

代码语言:javascript
复制
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}

有一个value参数,类型为RetentionPolicy枚举,如下:

代码语言:javascript
复制
public enum RetentionPolicy {
    /*注解只保留在源码中,编译为字节码之后就丢失了,也就是class文件中就不存在了*/
    SOURCE,
    /*注解只保留在源码和字节码中,运行阶段会丢失*/
    CLASS,
    /*源码、字节码、运行期间都存在*/
    RUNTIME
}

使用注解

语法

将注解加载使用的目标上面,如下:

代码语言:javascript
复制
@注解名称(参数1=值1,参数2=值2,参数n=值n)
目标对象

无参注解

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann1 { //@1
}

@Ann1 //@2
public class UseAnnotation1 {
}

@1:Ann1为无参注解

@2:类上使用@Ann1注解,没有参数

一个参数的注解

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann2 { //@1
    String name();
}

@Ann2(name = "我是java") //@2
public class UseAnnotation2 {

}

一个参数为value的注解,可以省略参数名称

只有一个参数,名称为value的时候,使用时参数名称可以省略

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann3 {
    String value();//@1
}

@Ann3("我是java") //@2
public class UseAnnotation3 {

}

@1:注解之后一个参数,名称为value

@2:使用注解,参数名称value省略了

数组类型参数

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann4 {
    String[] name();//@1
}

@Ann4(name = {"我是java", "欢迎和我一起学spring"}) //@2
public class UseAnnotation4 {
    @Ann4(name = "如果只有一个值,{}可以省略") //@3
    public class T1 {
    }
}

@1:name的类型是一个String类型的数组

@2:name有多个值的时候,需要使用{}包含起来

@3:如果name只有一个值,{}可以省略

为参数指定默认值

通过default为参数指定默认值,用的时候如果没有设置值,则取默认值,没有指定默认值的参数,使用的时候必须为参数设置值,如下:

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann5 {
    String[] name() default {"java", "spring系列"};//@1
    int[] score() default 1; //@2
    int age() default 30; //@3
    String address(); //@4
}

@Ann5(age = 32,address = "上海") //@5
public class UseAnnotation5 {

}

@1:数组类型通过{}指定默认值

@2:数组类型参数,默认值只有一个省略了{}符号

@3:默认值为30

@4:未指定默认值

@5:age=32对默认值进行了覆盖,并且为address指定了值

综合案例

代码语言:javascript
复制
@Target(value = {
        ElementType.TYPE,
        ElementType.METHOD,
        ElementType.FIELD,
        ElementType.PARAMETER,
        ElementType.CONSTRUCTOR,
        ElementType.LOCAL_VARIABLE
})
@Retention(RetentionPolicy.RUNTIME)
@interface Ann6 {
    String value();

    ElementType elementType();
}

@Ann6(value = "我用在类上", elementType = ElementType.TYPE)
public class UseAnnotation6 {
    @Ann6(value = "我用在字段上", elementType = ElementType.FIELD)
    private String a;

    @Ann6(value = "我用在构造方法上", elementType = ElementType.CONSTRUCTOR)
    public UseAnnotation6(@Ann6(value = "我用在方法参数上", elementType = ElementType.PARAMETER) String a) {
        this.a = a;
    }

    @Ann6(value = "我用在了普通方法上面", elementType = ElementType.METHOD)
    public void m1() {
        @Ann6(value = "我用在了本地变量上", elementType = ElementType.LOCAL_VARIABLE) String a;
    }
}

上面演示了自定义注解在在类、字段、构造器、方法参数、方法、本地变量上的使用,@Ann6注解有个elementType参数,我想通过这个参数的值来告诉大家对应@Target中的那个值来限制使用目标的,大家注意一下上面每个elementType的值。

@Target(ElementType.TYPE_PARAMETER)

这个是1.8加上的,用来标注类型参数,类型参数一般在类后面声明或者方法上声明,这块需要先了解一下泛型泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!不然理解起来比较吃力,来个案例感受一下:

代码语言:javascript
复制
@Target(value = {
        ElementType.TYPE_PARAMETER
})
@Retention(RetentionPolicy.RUNTIME)
@interface Ann7 {
    String value();
}

public class UseAnnotation7<@Ann7("T0是在类上声明的一个泛型类型变量") T0, @Ann7("T1是在类上声明的一个泛型类型变量") T1> {

    public <@Ann7("T2是在方法上声明的泛型类型变量") T2> void m1() {
    }

    public static void main(String[] args) throws NoSuchMethodException {
        for (TypeVariable typeVariable : UseAnnotation7.class.getTypeParameters()) {
            print(typeVariable);
        }

        for (TypeVariable typeVariable : UseAnnotation7.class.getDeclaredMethod("m1").getTypeParameters()) {
            print(typeVariable);
        }
    }

    private static void print(TypeVariable typeVariable) {
        System.out.println("类型变量名称:" + typeVariable.getName());
        Arrays.stream(typeVariable.getAnnotations()).forEach(System.out::println);
    }
}

类和方法上面可以声明泛型类型的变量,上面有3个泛型类型变量,我们运行一下看看效果:

代码语言:javascript
复制
类型变量名称:T0
@com.javacode2018.lesson001.demo18.Ann7(value=T0是在类上声明的一个泛型类型变量)
类型变量名称:T1
@com.javacode2018.lesson001.demo18.Ann7(value=T1是在类上声明的一个泛型类型变量)
类型变量名称:T2
@com.javacode2018.lesson001.demo18.Ann7(value=T2是在方法上声明的泛型类型变量)

@Target(ElementType.TYPE_USE)

这个是1.8加上的,能用在任何类型名称上,来个案例感受一下:

代码语言:javascript
复制
@Target({ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@interface Ann10 {
    String value();
}

@Ann10("用在了类上")
public class UserAnnotation10<@Ann10("用在了类变量类型V1上") V1, @Ann10("用在了类变量类型V2上") V2> {

    private Map<@Ann10("用在了泛型类型上") String, Integer> map;

    public <@Ann10("用在了参数上") T> String m1(String name) {
        return null;
    }

}

类后面的V1、V2都是类型名称,Map后面的尖括号也是类型名称,m1方法前面也定义了一个类型变量,名称为T

注解信息的获取

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

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

AnnotatedElement常用方法

案例

要解析的列如下

代码语言:javascript
复制
package com.javacode2018.lesson001.demo18;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;

@Target({ElementType.PACKAGE,
        ElementType.TYPE,
        ElementType.FIELD,
        ElementType.CONSTRUCTOR,
        ElementType.METHOD,
        ElementType.PARAMETER,
        ElementType.TYPE_PARAMETER,
        ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@interface Ann11 {
    String value();
}

@Target({ElementType.PACKAGE,
        ElementType.TYPE,
        ElementType.FIELD,
        ElementType.CONSTRUCTOR,
        ElementType.METHOD,
        ElementType.PARAMETER,
        ElementType.TYPE_PARAMETER,
        ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@interface Ann11_0 {
    int value();
}

@Ann11("用在了类上")
@Ann11_0(0)
public class UseAnnotation11<@Ann11("用在了类变量类型V1上") @Ann11_0(1) V1, @Ann11("用在了类变量类型V2上") @Ann11_0(2) V2> {
    @Ann11("用在了字段上")
    @Ann11_0(3)
    private String name;

    private Map<@Ann11("用在了泛型类型上,String") @Ann11_0(4) String, @Ann11("用在了泛型类型上,Integer") @Ann11_0(5) Integer> map;

    @Ann11("用在了构造方法上")
    @Ann11_0(6)
    public UseAnnotation11() {
        this.name = name;
    }

    @Ann11("用在了返回值上")
    @Ann11_0(7)
    public String m1(@Ann11("用在了参数上") @Ann11_0(8) String name) {
        return null;
    }

}

解析类上的注解

解析这部分

代码语言:javascript
复制
@Ann11("用在了类上")

代码

代码语言:javascript
复制
@Test
public void m1() {
    for (Annotation annotation : UserAnnotation10.class.getAnnotations()) {
        System.out.println(annotation);
    }
}

运行输出

代码语言:javascript
复制
@com.javacode2018.lesson001.demo18.Ann11(value=用在了类上)
@com.javacode2018.lesson001.demo18.Ann11_0(value=0)

解析类上的类型变量

解析类名后面的尖括号的部分,即下面的部分:

代码语言:javascript
复制
UseAnnotation11<@Ann11("用在了类变量类型V1上") @Ann11_0(1) V1, @Ann11("用在了类变量类型V2上") @Ann11_0(2) V2>

用例代码

代码语言:javascript
复制
@Test
public void m2() {
    TypeVariable<Class<UserAnnotation10>>[] typeParameters = UserAnnotation10.class.getTypeParameters();
    for (TypeVariable<Class<UserAnnotation10>> typeParameter : typeParameters) {
        System.out.println(typeParameter.getName() + "变量类型注解信息:");
        Annotation[] annotations = typeParameter.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}

运行输出

代码语言:javascript
复制
V1变量类型注解信息:
@com.javacode2018.lesson001.demo18.Ann11(value=用在了类变量类型V1上)
@com.javacode2018.lesson001.demo18.Ann11_0(value=1)
V2变量类型注解信息:
@com.javacode2018.lesson001.demo18.Ann11(value=用在了类变量类型V2上)
@com.javacode2018.lesson001.demo18.Ann11_0(value=2)

解析字段name上的注解

用例代码

代码语言:javascript
复制
@Test
public void m3() throws NoSuchFieldException {
    Field nameField = UserAnnotation10.class.getDeclaredField("name");
    for (Annotation annotation : nameField.getAnnotations()) {
        System.out.println(annotation);
    }
}

运行输出

代码语言:javascript
复制
@com.javacode2018.lesson001.demo18.Ann11(value=用在了字段上)
@com.javacode2018.lesson001.demo18.Ann11_0(value=3)

解析泛型字段map上的注解

用例代码

代码语言:javascript
复制
@Test
public void m4() throws NoSuchFieldException, ClassNotFoundException {
    Field field = UseAnnotation11.class.getDeclaredField("map");
    Type genericType = field.getGenericType();
    Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();

    AnnotatedType annotatedType = field.getAnnotatedType();
    AnnotatedType[] annotatedActualTypeArguments = ((AnnotatedParameterizedType) annotatedType).getAnnotatedActualTypeArguments();
    int i = 0;
    for (AnnotatedType actualTypeArgument : annotatedActualTypeArguments) {
        Type actualTypeArgument1 = actualTypeArguments[i++];
        System.out.println(actualTypeArgument1.getTypeName() + "类型上的注解如下:");
        for (Annotation annotation : actualTypeArgument.getAnnotations()) {
            System.out.println(annotation);
        }
    }
}

运行输出

代码语言:javascript
复制
java.lang.String类型上的注解如下:
@com.javacode2018.lesson001.demo18.Ann11(value=用在了泛型类型上,String)
@com.javacode2018.lesson001.demo18.Ann11_0(value=4)
java.lang.Integer类型上的注解如下:
@com.javacode2018.lesson001.demo18.Ann11(value=用在了泛型类型上,Integer)
@com.javacode2018.lesson001.demo18.Ann11_0(value=5)

解析构造函数上的注解

用例代码

代码语言:javascript
复制
@Test
public void m5() {
    Constructor<?> constructor = UseAnnotation11.class.getConstructors()[0];
    for (Annotation annotation : constructor.getAnnotations()) {
        System.out.println(annotation);
    }
}

运行输出

代码语言:javascript
复制
@com.javacode2018.lesson001.demo18.Ann11(value=用在了构造方法上)
@com.javacode2018.lesson001.demo18.Ann11_0(value=6)

解析m1方法上的注解

用例代码

代码语言:javascript
复制
@Test
public void m6() throws NoSuchMethodException {
    Method method = UseAnnotation11.class.getMethod("m1", String.class);
    for (Annotation annotation : method.getAnnotations()) {
        System.out.println(annotation);
    }
}

运行输出

代码语言:javascript
复制
@com.javacode2018.lesson001.demo18.Ann11(value=用在了返回值上)
@com.javacode2018.lesson001.demo18.Ann11_0(value=7)

解析m1方法参数注解

用例代码

代码语言:javascript
复制
@Test
public void m7() throws NoSuchMethodException {
    Method method = UseAnnotation11.class.getMethod("m1", String.class);
    for (Parameter parameter : method.getParameters()) {
        System.out.println(String.format("参数%s上的注解如下:", parameter.getName()));
        for (Annotation annotation : parameter.getAnnotations()) {
            System.out.println(annotation);
        }
    }
}

运行输出

代码语言:javascript
复制
参数arg0上的注解如下:
@com.javacode2018.lesson001.demo18.Ann11(value=用在了参数上)
@com.javacode2018.lesson001.demo18.Ann11_0(value=8)

上面参数名称为arg0,如果想让参数名称和源码中真实名称一致,操作如下:

代码语言:javascript
复制
如果你编译这个class的时候没有添加参数–parameters,运行的时候你会得到这个结果:

Parameter: arg0

编译的时候添加了–parameters参数的话,运行结果会不一样:

Parameter: args

对于有经验的Maven使用者,–parameters参数可以添加到maven-compiler-plugin的配置部分:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
        <compilerArgument>-parameters</compilerArgument>
        <source>1.8</source>
        <target>1.8</target>
    </configuration>
</plugin>
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/11/15 ,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring系列第十七讲
  • 什么是注解?
    • 注解如何使用?
    • 定义注解
      • 定义注解语法
        • 注解中定义参数
          • 指定注解的使用范围:@Target
            • 指定注解的保留策略:@Retention
            • 使用注解
              • 语法
                • 无参注解
                  • 一个参数的注解
                    • 一个参数为value的注解,可以省略参数名称
                      • 数组类型参数
                        • 为参数指定默认值
                          • 综合案例
                            • @Target(ElementType.TYPE_PARAMETER)
                              • @Target(ElementType.TYPE_USE)
                              • 注解信息的获取
                                • AnnotatedElement常用方法
                                  • 案例
                                    • 解析类上的注解
                                    • 解析类上的类型变量
                                    • 解析字段name上的注解
                                    • 解析泛型字段map上的注解
                                    • 解析构造函数上的注解
                                    • 解析m1方法上的注解
                                    • 解析m1方法参数注解
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档