Java提升七:注解

1、为何要使用注解?

在各种框架中,经常使用XML文件作为配置文件,从而实现框架中程序编写的解耦。但是随着开发的内容越来越复杂,对于框架中的所有对象进行XML配置将会使配置文件的维护成本急剧增加。

注解就是在这一条件下提出的解决方法,它依附于代码(类、方法以及属性)上,降低了配置时的维护成本,但是同时也增加了代码的耦合程度,所以现阶段框架中一般都支持XML配置文件和注解同时使用,在保证一定解耦的情况下,简化开发的过程。

2、注解的定义

(1)注解本质上就是继承了Annotation接口的一个接口。

(2)从注解的作用角度来说,注解也叫元数据,是一种描述数据的数据,主要用于对代码做出标识,从而对于标识过的代码进行一系列操作,它可以作用于包、类、接口、方法以及属性中。

(3)注解的组成部分

  • 定义注解的元注解
  • 注解名称(@Interface 注解名)
  • 注解属性(可有可无):注解只有属性,但是注解属性的定义方式是正常类的方法定义方式。

3、注解的分类与详解

Java中注解的继承体系如下:
注解继承框架
从上图可知:一个Annotation接口会与多个ElementType枚举类和一个RetentionPolicy枚举类相关联。

ElementType枚举类,定义了注解要作用的位置,在@Target注解中经常使用:

public enum ElementType {
    TYPE,//注解作用于类、接口和枚举上
    
    FIELD,//注解作用于属性上
    
    METHOD,//注解作用于方法上

    PARAMETER,//注解作用于方法中的参数上

    CONSTRUCTOR,//注解作用于构造器方法上

    LOCAL_VARIABLE,//注解作用于局部变量上

    ANNOTATION_TYPE,//注解作用于注解上

    PACKAGE,//注解作用于一个包上

    TYPE_PARAMETER,//注解作用于声明的变量上上

    TYPE_USE,

    MODULE//注解作用于一个模块上
}

RetentionPolicy枚举类,定义了注解作用的时间段,在@Retention注解中经常使用:

public enum RetentionPolicy {
    SOURCE,//注解在编译器阶段失效
    
    CLASS,//注解在运行时阶段失效
    
    RUNTIME//注解在运行时还是会保留
}

而Java中的注解大致可分为三类:

  • 元注解
  • Java自带注解
  • 自定义注解

3.1、元注解

元注解即是用于定义其他注解的注解,在Java中共有五类:

3.1.1、@Target:定义注解作用的位置

(1)使用格式:

@Target(ElementType.(选择ElementType枚举类中的属性来决定注解作用的位置))

(2)可选属性及其含义,其实完全可以参照之上的ElementType枚举类的源代码,此处为了对应再次列出:

  • ElementType.TYPE:注解作用于类、接口和枚举上
  • ElementType.FIELD:注解作用于属性上
  • ElementType.METHOD:注解作用于方法上
  • ElementType. PARAMETER:注解作用于方法中的参数上
  • ElementType.CONSTRUCTOR:注解作用于构造器方法上
  • ElementType. LOCAL_VARIABLE:注解作用于局部变量上
  • ElementType.ANNOTATION_TYPE:注解作用于注解上
  • ElementType.PACKAGE:注解作用于一个包上
  • ElementType.TYPE_PARAMETER:注解作用于声明的变量上
  • ElementType.TYPE_USE:注解作用于一个使用的类型上
  • ElementType. MODULE:注解作用于一个模块上

(3)使用实例

这里,我们事先引入一个自定义的注解,只是为了演示@Target注解的使用方式,并不做过多说明,代码如下:

//该@Target注解说明,所定义的@MyAnno1注解可以作用于类、属性和方法上
@Target(value={ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
//若作用范围只有一个可定义为:@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@interface MyAnno1{

}

3.1.2、@Retention:定义注解所作用的时期

(1)使用格式

@Retention(RetentionPolicy.(RetentionPolicy枚举类中的属性))

(2)可选属性及其含义,其实完全可以参照之上的RetentionPolicy枚举类的源代码,此处为了对应再次列出:

  • RetentionPolicy.SOURCE:注解只保留在源代码阶段,在编译时就会失效;
  • RetentionPolicy.CLASS:注解只保留在Class对象阶段,不会进入JVM,在运行时会失效;
  • RetentionPolicy. RUNTIME:注解会进入JVM,在运行时会保留;

(3)使用实例:


@Target(value={ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
//定义的注解@MyAnno1,在运行时还是存在并可以获取的
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@interface MyAnno1{

}

3.1.3、@Documented:定义注解作用后是否会生成文档

该注解中并未定义任何要赋值的属性值,故只要该注解存在,即可以生成文档;不存在即不可生成文档。

3.1.4、@Inherit:注解所作用类被继承时,该注解是否会被继承

该注解中并未定义任何要赋值的属性值。故在定义注解时,只要@Inherit注解存在,则所定义的注解,在作用于一个类之后,该类被继承时,所定义的注解也会被继承;反之则不可被继承。

3.1.5、@Repeatable:注解是否可以在同一个作用点被使用多次

@Repeatable注解的使用步骤详解:

  • ① 先定义一个含有要赋值的成员属性的自定义注解,其中成员属性的类型应该是一个自定义注解的类型。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Accounts {
    //自定义注解中要赋值的成员属性
    Account[] value();
}
  • ② 对于@Accounts注解中成员属性类型的@Account注解进行定义,此处便要使用@Repeatable注解。
@Repeatable(Accounts.class)
@interface  Account{
    String bank() default "农业银行";
}
  • ③ 以上定义步骤完成后,在类上使用@Account注解,使用实例如下:
@Account(bank="中国银行")
@Account(bank="工商银行")
@Account(bank="建设银行")
class CreateAccount{
    String bank="";
}

以上三个步骤展示了使用@Repeatable注解定义其它注解的使用方法,以及被@Repeatable注解定义的注解的使用示例。

3.2、Java自带注解

对于Java中自带的注解,我们可以参照其源码进行理解,并掌握其所代表的含义即可。

3.2.1、@Override

  • 注解源码:
@Target(ElementType.METHOD)//该注解作用于方法上
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
  • 注解作用:检查该方法是否是重写方法。
  • 使用格式:直接使用

3.2.2、@Deprecated

  • 注解源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
//该注解可作用于几乎所有代码上
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
    String since() default "";
    boolean forRemoval() default false;
}
  • 注解作用:标记过时代码。
  • 使用格式:直接使用

3.2.3、@Deprecated

  • 注解源码:
//该注解可作用于几乎所有代码上
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
	//由于存在成员属性value,使用该注解时必须赋值
    String[] value();
}
  • 注解作用:指示编译器去忽略注解中声明的警告,注解中成员属性value所要赋予的值就是要忽略的警告的名称,如"deprecation", "unchecked"等。
  • 使用格式:@SuppressWarnings (value={"deprecation", "unchecked"})

3.2.4、@FunctionalInterface

  • 注解源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
//该注解作用于接口上
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
  • 注解作用:用于限制一个接口是一个函数式接口。
  • 使用格式:直接使用

3.2.5、@SafeVarargs

  • 注解源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
//该注解作用于构造器方法和方法上
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
  • 注解作用:用于提醒开发者不要用参数做一些不安全的操作。
  • 使用格式:直接使用

3.3、自定义注解

在之前,已经粗略探讨过注解的组成部分,主要分为三大块:元注解,注解名和注解属性,接下来就结合代码来解析如何自定义一个注解以及自定义注解后有什么用处。

  • ① 首先,我们事先定义一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
/*-----以上为元注解---*/
@interface Check{//->注解名称

//此处未定义成员属性,若定义成员属性,
//格式为:成员属性类型 成员属性名称();
}
  • ② 使用自定义的注解
public class AnnoUse {
    //加法
    @Check
    public void add(){
        String str = null;
        str.toString();
        System.out.println("1 + 0 =" + (1 + 0));
    }
    //减法
    @Check
    public void sub(){
        System.out.println("1 - 0 =" + (1 - 0));
    }
    //乘法
    @Check
    public void mul(){
        System.out.println("1 * 0 =" + (1 * 0));
    }
    //除法
    @Check
    public void div(){
        System.out.println("1 / 0 =" + (1 / 0));
    }
}
  • ③使用了注解之后的操作
 public static void main(String[] args) throws IOException {
        //执行时,自动检测所有加了Check注解的方法
        // 判断方法是否存在异常,记录到文件中
        AnnoUse au=new AnnoUse();
        //2.获取字节码文件对象
        Class cls = au.getClass();
        //3.获取所有方法
        Method[] methods = cls.getMethods();

        int number = 0;//出现异常的次数
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
        for (Method method : methods) {
        
            //4.判断方法上是否有Check注解
            if(method.isAnnotationPresent(Check.class)){
                //5.有,执行
                try {
                    method.invoke(au);
                } catch (Exception e) {
                    //6.捕获异常
                    //记录到文件中
                    number ++;
                    bw.write(method.getName()+ " 方法出异常了");
                    bw.newLine();
                    bw.write("异常的名称:" + e.getCause().getClass().getSimpleName());
                    bw.newLine();
                    bw.write("异常的原因:"+e.getCause().getMessage());
                    bw.newLine();
                    bw.write("--------------------------");
                    bw.newLine();

                }
            }
        }
        bw.write("本次测试一共出现 "+number+" 次异常");
        bw.flush();
        bw.close();
    }

由以上流程,可以知道注解的自定义过程以及使用注解的意义。注解往往只是一种标识,需要和反射相结合才能从代码中选出相应的部分作出相应操作或获取相应信息,这也是注解被各个框架所广泛使用的原因。

4、总结

注解作为一种标记性的数据,在框架中作为反射时获取相应信息的选择器条件,在一定程度上从当了xml配置文件的作用。而现阶段使用框架时,往往需要将xml配置文件和注解相结合来简化开发,且注解因其简便性往往占据较大比重,故此需要深刻理解注解的底层原理并要灵活使用。在后期,可以结合框架来深入理解注解的妙用。

posted @ 2020-03-08 22:23  zjL1997  阅读(313)  评论(0编辑  收藏  举报