认识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中的预置注解
这个元素是用来标记过时的元素,想必大家在日常开发中经常碰到。编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。
这个大家应该很熟悉了,提示子类要复写父类中被 @Override 修饰的方法
阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过 @SuppressWarnings 达到目的。
参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。
注解的提取
要想正确检阅注解,离不开一个手段,那就是反射。
通关反射,可以获取对象的类对象,类对象包含各种属性以及构造方法和普通方法的对象,这些对象可以获取注解相关的信息。
注解与反射:注解通过反射获取,反射与注解主要的方法:
- 判断有无注解
首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
- 获取注解
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信息,是否和注解标注的一致
}
输出信息如下:
可以看到,和我们在实体类上加入注解所想达到的效果是一样的!
这样就完成了类似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 + '\'' + '}'; }}
再次运行结果如下:
可以看到,该Goods对象的属性值已经变了
其实,我们平常使用Spring提供的注解,方便之处就在于,帮我们屏蔽了大量底层的东西,Spring帮我们简化我们的代码如下所示:
public static void main(String[] args) {
User user = new User() ;
System.out.println(user);
}
其实在创建这个对象的时候,底层做的赋值大概就是上面那样,通过反射和注解的形式来完成。