你真的知道Java注解吗?

认识Java注解

文章原文
www.kunstudy.com

  • 简单来说,Java注解就是一种标识,一种标签,其作用是给运行程序看的。

  • 运行中的程序可以判断和获取指定位置有无注解,或者该位置的注解类型,或者注解内容,从而做出不同的逻辑处理。通过简单的配置就可以完成复杂的工作。

  • 注解可以放在类上,方法上,属性上,参数上,包上,注解上,局部变量上。

例如下面的代码,就定义了一个自定义注解以及简单的使用,并无实际意义

//定义注解
public @interface MyAnnotation {
}
//使用注解
@MyAnnotation
public class AnnotationTest {
}

接下来,不得不介绍一下元注解:

元注解

什么是元注解呢?

  • 元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。
  • 它的作用和目的就是给其他普通的注解进行解释说明的。

元注解有哪些?

  • @Retention:被它所注解的注解保留多久,有三个值

SOURCE:注解只保留在源文件

CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期

RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在

选择什么生命周期合适呢?

首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。

  • @Documented

顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。

  • @Target

Target 是目标的意思,@Target 指定了注解运用的地方。@Target 有下面的取值:

ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举

  • @Inherited

Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。

  • @Repeatable

Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。

注解的属性:

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

public @interface MyAnnotation {
    int id() ;
    String name() ;
}

上面的代码定义了一个注解,里面定义了两个属性,在使用该注解时应该指明这两个属性的值

@MyAnnotation(id = 1,name = "张三")
public class AnnotationTest {
}

需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。

注解中属性可以有默认值,默认值需要用 default 关键值指定。比如:

public @interface MyAnnotation {
    int id() default 0;
    String name() default "张三";
}

另外,还有一种情况。如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。

Java中的预置注解

  • @Deprecated

这个元素是用来标记过时的元素,想必大家在日常开发中经常碰到。编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。

  • @Override

这个大家应该很熟悉了,提示子类要复写父类中被 @Override 修饰的方法

  • @SuppressWarnings

阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过 @SuppressWarnings 达到目的。

  • @SafeVarargs

参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。

注解的提取

要想正确检阅注解,离不开一个手段,那就是反射。

通关反射,可以获取对象的类对象,类对象包含各种属性以及构造方法和普通方法的对象,这些对象可以获取注解相关的信息。

注解与反射:注解通过反射获取,反射与注解主要的方法:

  1. 判断有无注解

首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解

public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
  1. 获取注解
 public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
 public Annotation[] getAnnotations() {}

前一种方法返回指定类型的注解,后一种方法返回注解到这个元素上的所有注解。

如果获取到的 Annotation 如果不为 null,则就可以调用它们的属性方法了

自定义注解实现对象的创建和属性的赋值

1.创建自定义注解

//自定义注解,利用该注解可以自动创建对象
@Retention(RetentionPolicy.RUNTIME)//运行时阶段
@Target({ElementType.TYPE,ElementType.FIELD})//作用在类上
public @interface Bean {
}
//该注解可以定义在类的属性上,完成属性的赋值
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Field {
    String value() ;
}

2.创建测试对象

测试对象,均为简单的实体类,并未提供get和set方法,重写toString是为了方便调试。均采用暴力破解,直接赋值,稍后会提到。

//定义商品类
public class Goods {
    @Field("1")//为属性赋值
    private String id ;//商品id
    @Field("可乐")//为属性赋值
    private String name ;//商品名字
    @Override
    public String toString() {
        return "Goods{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
//定义用户类
public class User {
    @Field("1")
    private String id ;//用户id
    @Field("张三")
    private String name ;//用户名
    @Bean//创建对象
    private Goods goods ;//用户的商品

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", goods=" + goods +
                '}';
    }
}

3.测试

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
       User user = new User() ;//创建user对象
       //获取该类的Class对象
       Class<? extends User> userClass = user.getClass();
       //获取所有属性,不考虑修饰符
       Field[] userFields = userClass.getDeclaredFields();
       //遍历
       for (Field userField : userFields) {
           //得到每个Field对象
           //为了简单,这里直接判断有无此注解,不得到所有注解进行遍历判断了
           if (userField.isAnnotationPresent(com.kunstudy.annotation.Field.class)) {//包含属性注解
               //有此注解
               //获取该注解的对象
               com.kunstudy.annotation.Field annotation = userField.getAnnotation(com.kunstudy.annotation.Field.class);
               //获取值
               String value = annotation.value();
               //暴露赋值
               userField.setAccessible(true);
               //为该属性赋值
               userField.set(user,value);
           }
           if (userField.isAnnotationPresent(Bean.class)) {//包含Bean的注解
               Bean annotation = userField.getAnnotation(Bean.class);
               //利用该属性值为名字,创建对象
               Class<?> beanClass = userField.getType();//返回该属性的Class对象
               Constructor<?> declaredConstructor = beanClass.getDeclaredConstructor();//获取的为默认的无参构造
               Goods goods = (Goods) declaredConstructor.newInstance();//利用无参构造创建对象
               Field[] declaredFields = beanClass.getDeclaredFields();
               for (Field declaredField : declaredFields) {
                   if (declaredField.isAnnotationPresent(com.kunstudy.annotation.Field.class)) {
                       com.kunstudy.annotation.Field annotation1 = declaredField.getAnnotation(com.kunstudy.annotation.Field.class);
                       String value = annotation1.value();
                       declaredField.setAccessible(true);
                       declaredField.set(goods,value);
                   }
               }
               //调用User属性对象的set方法为goods赋值
               Field goodsField = userClass.getDeclaredField("goods");
               goodsField.setAccessible(true);
               goodsField.set(user,goods);
           }
       }
       System.out.println(user);//打印user信息,是否和注解标注的一致
   }

输出信息如下:

QQ截图20210814214122

可以看到,和我们在实体类上加入注解所想达到的效果是一样的!

这样就完成了类似Spring框架给我们提供的自动注入的功能,当然Spring自动注入的底层还是复杂的多的。

我们可以改变一下注解的内容不妨试一试,将Goods类修改如下:

//定义商品类public class Goods {    @Field("2")    private String id ;//商品id    @Field("薯条")    private String name ;//商品名字    @Override    public String toString() {        return "Goods{" +                "id=" + id +                ", name='" + name + '\'' +                '}';    }}

再次运行结果如下:

QQ截图20210814214647

可以看到,该Goods对象的属性值已经变了

其实,我们平常使用Spring提供的注解,方便之处就在于,帮我们屏蔽了大量底层的东西,Spring帮我们简化我们的代码如下所示:

public static void main(String[] args) {
       User user = new User() ;
       System.out.println(user);
   }

其实在创建这个对象的时候,底层做的赋值大概就是上面那样,通过反射和注解的形式来完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值