Java注解详解

​ 这部分内容是网上找的资源学的,之前也看过一些博客和书,但是个人觉得这篇算是讲的最清楚,最容易理解的。花一个小时学完基本都能猜到Spring注解(比如@Controller,@AutoWired)的运行机制了。

1. 概念和分类

概念:说明程序的。给计算机看的

  • 注释:用文字描述程序的。给程序员看的

  • 定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

  • 概念描述:

    • JDK1.5之后的新特性
    • 说明程序的
    • 使用注解:@注解名称
  • 作用分类:
    ①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】我们自己不能改的,比如@since
    ②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
    ③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】我们自己不能改的,比如@override

对于编写文档,下面来使用javadoc生成哥文档,做个小测试如下:

package com.scu.annotation;

/**
 * 关于商品操作的Dao,提供给服务层调用
 * @author 127067
 * @since jdk 1.8
 */
public class ItemDao {
    /**
     *根据商品id查询商品信息
     * @param id  商品id
     * @return  商品信息
     */
    public String findById(int id){
        return "小米手机9"+"3299"+"6G+128G"+"王源代言";
    }
}

在桌面新建立个文件夹,将这个ItemDao.java文件复制到文件夹中,把类中的包com.scu.annotation删掉(不删其实也行,但是生成的文档就出现了所在的包)。还需要将文件编码设置为ANSI编码否则编译时中文会乱码并且报错。使用命令行 cmd进入,使用命令javadoc ItemDao.java生成文档。接下来该文件夹下会多出很多文件

在这里插入图片描述

点击index.html,这就是我们生成的完整doc文档

在这里插入图片描述

注意到,@Since,@Author的信息都在类的介绍上面,关于方法的信息也有,这里就不贴出来了。

2. jdk中预定义的注解

  • JDK中预定义的一些注解
    • @Override :检测被该注解标注的方法是否是继承自父类(接口)的
    • @Deprecated:该注解标注的内容,表示已过时
    • @SuppressWarnings:压制警告
      • 一般传递参数all @SuppressWarnings(“all”)

@Override就是为了防止方法重写的时候名字写错了,比如父类方法名findById子类本来打算重写的,但是手误写成了fnidById,此时加上@Override注解,那么编译就会报错,因为父类并没有fnidById方法。

java.util.Date类中就有很多方法被标注了@Deprecated注解,表示这个方法将来会被弃用,如果我们强行使用,那么会出现如下提示:

Date date = new Date();
int day = date.getDay();//getDate()中间会多一横杠

@SuppressWarnings注解用来压制警告,比如之前写的ItemDao类上会出现提示,该类没有被使用。如果想不出现提示的话就使用该注解。通常使用到类,方法,域(局部变量上也行),构造方法上。

3. 自定义注解

格式:

元注解
public @interface 注解名称{
	属性列表;
}

本质:注解本质上就是一个接口,该接口默认继承Annotation接口 创建MyAnno注解 先编译然后再使用反编译,如下

public @interface MyAnno {
}

将该java文件复制到文件夹下,先编译(多出来MyAnno.class文件)再反编译

PS C:\Users\lenovo\Desktop\demo> javac .\MyAnno.java
PS C:\Users\lenovo\Desktop\demo> javap .\MyAnno.class
Compiled from "MyAnno.java"
public interface com.scu.annotation.MyAnno extends java.lang.annotation.Annotation {
}

可以看到注解本质上就是一个接口。

属性:接口中的抽象方法
* 要求:
1. 属性的返回值类型有下列取值

	1. 基本数据类型
	
	2. String类型
	
	3. 枚举
	
	4. 注解   不要带`@`
	
	5. 以上类型的数组
	
	   注意:属性名称(即方法名称)最好定义成属性名的形式,因为使用的时候是`方法名

public @interface MyAnno {
	       int age();//注意:不能带参数age(int x)不行
	       String name();
	       MyAnno2 anno();//注解
	       Animal dog();//枚举  DOG,CAT;
	       int[] arrs();
}	
  1. 定义了属性,在使用时需要给属性赋值
    1. 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
    1. 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
    2. 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略

对于2.1 使用注解的时候,属性必须赋值,如下:

@MyAnno(age = 18)//18表示返回值
public class ItemDao {

    public String findById(int id){
        return "小米手机9"+"3299"+"6G+128G"+"王源代言";
    }
}

如果定义的时候加了default则不需要赋值,如下。但是如果有多个属性的话,没有default的属性,再使用的时候仍然要赋值。

public @interface MyAnno {
    int age() default 18;
}

@MyAnno
public class ItemDao {

    public String findById(int id){
        return "小米手机9"+"3299"+"6G+128G"+"王源代言";
    }
}

对于2.2 如果定义的属性名为value那么使用的时候就不需要指定属性名。但是如果要使用多个属性的时候需要指明value=xxx。如下:

public @interface MyAnno {
    String value();
}

@MyAnno("hello")// 不需要指定value="hello"
public class ItemDao {

    public String findById(int id){
        return "小米手机9"+"3299"+"6G+128G"+"王源代言";
    }
}

对于2.3,举例如下:

public @interface MyAnno {
    String value();
    String[] strs();
}

@MyAnno(value = "hello",strs = {"Tom","Jack","Lucy"})
public class ItemDao {

    public String findById(int id){
        return "小米手机9"+"3299"+"6G+128G"+"王源代言";
    }
}

4. 元注解

元注解:用于描述注解的注解

@Target:描述注解能够作用的位置

  1. ElementType取值:

    • TYPE:可以作用于类上
    • METHOD:可以作用于方法上
    • FIELD:可以作用于成员变量上
  2. @Retention:描述注解被保留的阶段 三个阶段

    @Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到 如果是CLASS则也会保留到字节码文件中但是不会被jvm读取到 SOURCE则都不可以 (Override就是SOURCE这个阶段)

  3. @Documented:描述注解是否被抽取到api文档中 使用javadoc生成后能够看到

  4. @Inherited:描述注解是否被子类继承

对于Target,来看源码中仅有的一个属性value

ElementType[] value();

ElementType枚举中的类型主要是TYPE,METHOD,FIELD

对于3,如果自定义注解使用了元注解@Documented,那么使用了该自定义注解的类再生成javadoc的时候会保留该注解。通常我们主要使用前两个即可。

5. 解析注解

在程序使用(解析)注解:获取注解中定义的属性值 替换反射中使用的配置文件,配置文件中指定类名和方法名
1. 获取注解定义的位置的对象 (Class,Method,Field)
2. 获取指定的注解
* getAnnotation(Class)
//其实就是在内存中生成了一个该注解接口的子类实现对象

            public class ProImpl implements Pro{
                public String className(){
                    return "com.scu.annotation.User";
                }
                public String methodName(){
                    return "show";
                }
            }

  1. 调用注解中的抽象方法获取配置的属性值

根据以上步骤和反射知识来实现

public class User {
    public void show(){
        System.out.println("hello world!");
    }
}



@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Prop {
    String className();
    String methodName();
}


/**
 * 框架类
 * 前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法
 */
@Prop(className = "com.scu.annotation.User",methodName = "show")
public class PropTest {
    public static void main(String[] args) throws Exception {
        //1.解析注解
        //1.1获取该类的字节码文件对象
        Class<PropTest> clazz = PropTest.class;
        //2.获取上边的注解对象
        //其实就是在内存中生成了一个该注解接口的子类实现对象
        /*
            public class ProImpl implements Pro{
                public String className(){
                    return "com.scu.annotation.User";
                }
                public String methodName(){
                    return "show";
                }

            }
        */
        Prop annotation = clazz.getAnnotation(Prop.class);
        //3.调用注解对象中定义的抽象方法,获取返回值
        String className = annotation.className();
        String methodName = annotation.methodName();
        //3.加载该类进内存
        Class cls = Class.forName(className);
        //4.创建对象
        Object instance = cls.newInstance();
        //5.获取方法对象
        Method method = cls.getMethod(methodName);
        //6.执行方法
        method.invoke(instance);

    }
}
//运行后输出 "hello world!"

这样就使用Prop来替换掉了配置文件prop.properties,需要修改类名和方法名的时候,只需要再使用的地方(解析的地方)进行属性指定即可。

6. 测试框架案例

现有注解Check和计算器类Calculator如下,其中定义了4个运算方法以及一个show方法,我们需要做的是,测试这四个方法中哪个有问题。

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

/**
 * 计算器类
 */
public class Calculator {

    //加法
    @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 void show(){
        System.out.println("永无bug...");
    }

}

我们需要通过解析注解来测试哪些方法有问题,并且写到日志当中。

public class CheckTest {
    public static void main(String[] args) throws Exception{
        Class cls = Class.forName("com.scu.annotation.Calculator");
        Object instance = cls.newInstance();
        Method[] methods = cls.getMethods();
        int count = 0;
        PrintWriter pw = new PrintWriter("bug.txt");
        for (Method method : methods) {//使用iter快捷方式
            if(method.isAnnotationPresent(Check.class)) {
                try {
                    method.invoke(instance);
                }catch (Exception e) {
                    count++;
                    pw.println("出现异常了");
                    pw.println("异常的名称:" + e.getCause().getClass().getName());
                    pw.println("异常的原因:" + e.getCause().getMessage());
                    pw.println("-----------------------");
                }
            }
        }
        pw.println("本次测试以一共出现:"+count+"次异常");
        pw.close();
    }
}

运行后控制台输出

src同级目录下出现bug.txt,内容如下:

出现异常了
异常的名称:java.lang.NullPointerException
异常的原因:null
-----------------------
出现异常了
异常的名称:java.lang.ArithmeticException
异常的原因:/ by zero
-----------------------
本次测试以一共出现:2次异常

小结

  1. 以后大多数时候,我们会使用注解,而不是自定义注解

  2. 注解给谁用?

    1. 编译器
    2. 给解析程序用 比如上面的CheckTest
  3. 注解不是程序的一部分,可以理解为注解就是一个标签 还有就是属性不能带参数

  • 13
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值