009_注解机制

我们要学习一个东西,肯定要知道它的作用。相信没有 Java 程序猿不知道 Spring 框架的,Spring 框架中定义了大量的注解,基于注解,我们可以做到几乎零配置的开发商业项目,如 SpringBoot 就将原来 Spring 使用 XML 进行配置全部转换为使用注解达到相同的功效。本来是个非常普通直白的功能,Spirng可算是把注解玩出了花来。

注解存在的意义

代码的运行,本质上就是告诉CPU如何执行,执行的过程伴随着数据流的流动。同样的,你也可以理解成用数据来控制行为。那么问题就来了,我们java内部来传递信息可以使用简单数据类型,也可以使用一个类进行包裹。有些代码的执行需要部分信息指导,但是这部分信息每个都要使用类来承载会导致项目内类泛滥。注解的出现就是让小信息通过一种优雅的方式告诉整个虚拟机。

例如我们的测试类:

public class Test {
    @Test
    public void test001(){
        System.out.println("Test.....");
    }
}

其中@Test就是一个注解,在运行该方法时,测试框架会自动识别该方法并单独调用,运行时告诉测试框架该方法为测试方法。

注解的语法

我们先使用上面看到过的测试注解,来感受一下注解内部需要关心的信息。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {

}

首先,需要告诉编译器这个类的性质是一个注解,这就需要使用@interface来声明Test注解。从代码上看注解的定义很像接口的定义,确实如此,毕竟在编译后也会生成Test.class文件

然后,我们的注解是需要针对我们的类成分做限制的,这就用到了@Target注解,传入ElementType.METHOD参数来标明@Test只能用于方法上。

再然后,我们还需要限制注解提供的信息的生命周期,这就用到了@Retention,RetentionPolicy.RUNTIME用来表示该注解生存期是运行时。

其中,@Target和@Retention是由Java提供的元注解,所谓元注解就是标记其他注解的注解。

元注解

所谓元注解就是,一种基本注解,可以应用在其它的注解上,也就是说,元注解是用于标记注解的注解。

元注解有6个:

  • java.lang.annotation.Retention
  • java.lang.annotation.Target
  • java.lang.annotation.Documented
  • java.lang.annotation.Inherited
  • java.lang.annotation.Repeatable
  • java.lang.annotation.Native

@Retention元注解

Retention 是保留的意思,表明注解产生的时间范围。其值是 java.lang.RetentionPolicy枚举。

  • RetentionPolicy.SOURCE : 只在源代码级别保留有用,在编译期就丢弃了
  • RetentionPolicy.CLASS : 在编译期保留有效,在运行期(JVM中)开始丢弃;这是默认的保留策略
  • RetentionPolicy.RUNTIME : 在编译期、运行其都保留有效,所以可以在反射中使用
@Retention(RetentionPolicy.RUNTIME)
public @interface Demo {}

如果我们需要在运行时捕获成分上的注解信息,我们需要将该注解标记为RetentionPolicy.RUNTIME

@Documented

@Documented 表明注解的类型将会包含到Javadoc中去。

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

//没有使用@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentB {
}

//使用注解
@DocumentA
@DocumentB
public class DocumentDemo {
    public void A(){}
}

当你是用javadoc指令生成文档的时候,你会发现@DocumentB没有在文档内出现。

@Target

@Target 标明注解使用约束的应用上下文,是数组形式,可以标记在多个范围中使用。值由 java.lang.annotation.ElementType指定。java.lang.annotation.ElementType 的可用值如下:

public enum ElementType {
    /**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
    TYPE,

    /** 标明该注解可以用于字段(域)声明,包括enum实例 */
    FIELD,

    /** 标明该注解可以用于方法声明 */
    METHOD,

    /** 标明该注解可以用于参数声明 */
    PARAMETER,

    /** 标明注解可以用于构造函数声明 */
    CONSTRUCTOR,

    /** 标明注解可以用于局部变量声明 */
    LOCAL_VARIABLE,

    /** 标明注解可以用于注解声明(应用于另一个注解上)*/
    ANNOTATION_TYPE,

    /** 标明注解可以用于包声明 */
    PACKAGE,

    /**
     * 标明注解可以用于类型参数声明(1.8新加入)
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * 类型使用声明(1.8新加入)
     * @since 1.8
     */
    TYPE_USE
}

当注解未指定Target值时,则此注解可以用于任何元素之上,如果需要使用多个值,那么就使用{}包含并用逗号隔开。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
public @interface Demo {
}
//TYPE_PARAMETER 标注在类型参数上
class D<@Parameter T> { }

//TYPE_USE则可以用于标注任意类型(不包括class)
//用于父类或者接口
class Image implements @Rectangular Shape { }

//用于构造函数
new @Path String("/usr/bin")

//用于强制转换和instanceof检查,注意这些注解中用于外部工具,它们不会对类型转换或者instanceof的检查行为带来任何影响。
String path=(@Path String)input;
if(input instanceof @Path String)

//用于指定异常
public Person read() throws @Localized IOException.

//用于通配符绑定
List<@ReadOnly ? extends Person>
List<? extends @ReadOnly Person>

@NotNull String.class //非法,不能标注class
import java.lang.@NotNull String //非法,不能标注import

这里主要说明一下TYPE_USE,类型注解用来支持在Java的程序中做强类型检查,配合第三方插件工具(如Checker Framework),可以在编译期检测出runtime error(如UnsupportedOperationException、NullPointerException异常),避免异常延续到运行期才发现,从而提高代码质量,这就是类型注解的主要作用。总之Java 8 新增加了两个注解的元素类型ElementType.TYPE_USE 和ElementType.TYPE_PARAMETER ,通过它们,我们可以把注解应用到各种新场合中。

@Inherited

@Inherited 可以让注解被继承,但这并不是真的继承,只是通过使用@Inherited,可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解,如下:

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

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

@DocumentA
class A{ }

class B extends A{ }

@DocumentB
class C{ }

class D extends C{ }

//测试
public class DocumentDemo {

    public static void main(String... args){
        A instanceA=new B();
        System.out.println("已使用的@Inherited注解:"+Arrays.toString(instanceA.getClass().getAnnotations()));

        C instanceC = new D();
        System.out.println("没有使用的@Inherited注解:"+Arrays.toString(instanceC.getClass().getAnnotations()));
    }

    /**
     * 运行结果:
     已使用的@Inherited注解:[@com.zejian.annotationdemo.DocumentA()]
     没有使用的@Inherited注解:[]
     */
}

@Repeatable

元注解@Repeatable是JDK1.8新加入的,它表示在同一个位置重复相同的注解。在没有该注解前,一般是无法在同一个类型上使用相同的注解的。例如,在JDK1.8之前,这样的代码是编译报错的:

@FilterPath("/web/update")
@FilterPath("/web/add")
public class A {}

如果我们想实现上面类似的功能,就需要将@FilterPath注解时定义一个数组元素接收多个值。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FilterPath {
    String [] value();
}

//使用
@FilterPath({"/update","/add"})
public class A { }

但在Java8新增了@Repeatable注解后就可以采用如下的方式定义并使用了

@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
// 使用Java8新增@Repeatable原注解,参数指明接收的注解class
// 在反射的时候使用FilterPaths类进行获取数据
@Repeatable(FilterPaths.class)
public @interface FilterPath {
    String  value();
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface FilterPaths {
    FilterPath[] value();
}

//使用案例
@FilterPath("/web/update")
@FilterPath("/web/add")
@FilterPath("/web/delete")
class AA{}

内置注解

jdk内部自带一些注解,可以随时使用。

@Deprecated

表示废弃,在编译期会发出警告。

public class AnnoDemo {
    @Deprecated
    private static void sayHello(){

    }

    public static void main(String[] args){
        AnnoDemo.sayHello();
    }
}

@FunctionalInterface

函数式接口:一个具有一个方法的普通接口。

@Override

实现类要重写父类或者接口的方法

@SafeVarargs

参数安全类型注解,告诉开发者不要用参数做一些不安全的操作

@SuppressWarnings

阻止编译器发出告警,比如调用了使用了 @Deprecated 标记的方法编译器会发出警告,可以使用 @SuppressWarnings 压制警告。常见的值有:

关键词用途
allto suppress all warnings
boxingto suppress warnings relative to boxing/unboxing operations
castto suppress warnings relative to cast operations
dep-annto suppress warnings relative to deprecated annotation
deprecationto suppress warnings relative to deprecation
fallthroughto suppress warnings relative to missing breaks in switch statements
finallyto suppress warnings relative to finally block that don’t return
hidingto suppress warnings relative to locals that hide variable
incomplete-switchto suppress warnings relative to missing entries in a switch statement (enum case)
nlsto suppress warnings relative to non-nls string literals
nullto suppress warnings relative to null analysis
rawtypesto suppress warnings relative to un-specific types when using generics on class params
restrictionto suppress warnings relative to usage of discouraged or forbidden references
serialto suppress warnings relative to missing serialVersionUID field for a serializable class
static-accessto suppress warnings relative to incorrect static access
synthetic-accessto suppress warnings relative to unoptimized access from inner classes
uncheckedto suppress warnings relative to unchecked operations
unqualified-field-accessto suppress warnings relative to field access unqualified
unusedto suppress warnings relative to unused code

自定义注解

首先编写一个简单注解:

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

然后在注解内添加属性,注解的属性是以无参方法的形式声明的。这样一来你就可以使用这个注解为其他类进行标注:

public @interface Demo {
    String role();
}

@Demo(role = "t")
class A{}

针对属性的类型是有要求的,必须是以下的类型之一:

  • 所有基本类型(int,float,boolean,byte,double,char,long,short)
  • 引用类型:String,Class,enum,Annotation
  • 上述类型的数组
public @interface Demo {

    //声明枚举
    Status status();

    //布尔类型
    boolean showSupport();

    //String类型
    String name();

    //class类型
    Class<?> testCase();

    //注解嵌套
    Reference reference();

    //数组类型
    long[] value();
}

@Demo(
  		status = Status.A , 
      showSupport = true , 
      name="吃饭", 
      testCase=T.class , 
      reference=@Reference(next=true),
     	value= {1L,2L}
)
class A{}

编译器对元素的默认值是有要求的。首先,元素不能有不确定的值。也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。其次,对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以null作为值,为了绕开这个限制,只能定义一些特殊的值,例如空字符串或负数,表示某个元素不存在。

我们可以在注解内的属性后面使用 default关键字可以给属性设置默认值。这样一来,使用注解的时候就可以不需要指明其值。

public @interface Demo {
    String role() default "B";
}
@Demo
class A{}

如果你的属性名称为value,那么在使用注解的时候,可以省略名称,变成这样:

public @interface Demo {
    String value();
}

@Demo("t")
class A{}

3. 注解与反射

注解仅仅是用作标记,要想它真实发挥作用,就需要利用Java反射机制编写注解解析器,用作业务需求。

我们的一个类会有很多成分,这些成分都实现了AnnotatedElement接口,罗列一下会有:

  • Class:类的Class对象定义
  • Constructor:代表类的构造器定义
  • Field:代表类的成员变量定义
  • Method:代表类的方法定义
  • Package:代表类的包定义

AnnotatedElement接口下有这几个方法:

  • <A extends Annotation> getAnnotation(Class<A> annotationClass) : 该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。
  • Annotation[] getAnnotations() : 返回此元素上存在的所有注解,包括从父类继承的
  • boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) : 如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。
  • Annotation[] getDeclaredAnnotations() : 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组

这些成分都可以利用AnnotatedElement接口获得到对应成分上的注解实例。

@Target({ElementType.TYPE,
        ElementType.METHOD,
        ElementType.FIELD,
        ElementType.TYPE_USE,
        ElementType.PARAMETER,
        ElementType.CONSTRUCTOR})
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface AnnotationTest {
    String value() default "";
}


@AnnotationTest("类上的注解")
public class TestAnnotation<@AnnotationTest("类变量类型(泛型)上的注解") T0, T1> {
    
    @AnnotationTest("成员变量上的注解")
    private Map<@AnnotationTest("成员变量泛型上的注解") String, String> map;

    @AnnotationTest("成员方法上的注解")
    public void Test(@AnnotationTest("方法参数上的注解") String s, @AnnotationTest("方法参数上的注解2") String s2) {}

    @AnnotationTest("构造函数上的注解")
    public TestAnnotation() {}


    public static void main(String[] args) throws NoSuchMethodException {
        Class<TestAnnotation> clazz = TestAnnotation.class;
        //获取类上的注解
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        //是否存在注解
        if (clazz.isAnnotationPresent(AnnotationTest.class)) {
            AnnotationTest annotation1 = clazz.getAnnotation(AnnotationTest.class);
            System.out.println(annotation1.value());
        }


        //获取类的类型变量(泛型)的注解
        TypeVariable<Class<TestAnnotation>>[] typeParameters = clazz.getTypeParameters();
        for (TypeVariable typeVariable : typeParameters) {
            System.out.println(typeVariable.getName());
            Annotation[] annotations1 = typeVariable.getAnnotations();
            for (Annotation annotation : annotations1) {
                System.out.println(annotation);
            }
        }

        //获取成员变量上的注解
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field.getName());
            for (Annotation annotation : field.getAnnotations()) {
                System.out.println(annotation);
            }
        }

        //获取成员变量泛型的注解
        for (Field field : fields) {
            AnnotatedParameterizedType annotatedType = (AnnotatedParameterizedType) field.getAnnotatedType();
            AnnotatedType[] typeArguments = annotatedType.getAnnotatedActualTypeArguments();
            for (AnnotatedType typeArgument : typeArguments) {
                for (Annotation annotation : typeArgument.getAnnotations()) {
                    System.out.println(annotation);
                }
            }
        }

        //获取成员方法上的注解
        Method test = clazz.getMethod("Test", String.class, String.class);
        for (Annotation annotation : test.getAnnotations()) {
            System.out.println(annotation);
        }

        //获取成员方法上的参数注解
        for (Parameter parameter : test.getParameters()) {
            for (Annotation annotation : parameter.getAnnotations()) {
                System.out.println(annotation);
            }
        }

        //获取构造函数上的注解
        Constructor<TestAnnotation> constructor = clazz.getConstructor();
        for (Annotation annotation : constructor.getAnnotations()) {
            System.out.println(annotation);
        }

    }
}

运行输出:

@com.zifang.util.zex.bust.chapter9.AnnotationTest(value=类上的注解)
类上的注解
T0
@com.zifang.util.zex.bust.chapter9.AnnotationTest(value=类变量类型(泛型)上的注解)
T1
map
@com.zifang.util.zex.bust.chapter9.AnnotationTest(value=成员变量上的注解)
@com.zifang.util.zex.bust.chapter9.AnnotationTest(value=成员变量泛型上的注解)
@com.zifang.util.zex.bust.chapter9.AnnotationTest(value=成员方法上的注解)
@com.zifang.util.zex.bust.chapter9.AnnotationTest(value=方法参数上的注解)
@com.zifang.util.zex.bust.chapter9.AnnotationTest(value=方法参数上的注解2)
@com.zifang.util.zex.bust.chapter9.AnnotationTest(value=构造函数上的注解)
  • 9
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值