Spring里到处都是注解,而注解是依赖于反射,好好研究了一番
通常我们定义class类来修饰一种数据类型,而其实class本身也是一种数据类型,它的数据类型是Class
Class.java里定义如下
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
private static final int ANNOTATION= 0x00002000;
private static final int ENUM = 0x00004000;
private static final int SYNTHETIC = 0x00001000;
private static native void registerNatives();
static {
registerNatives();
}
/*
* Private constructor. Only the Java Virtual Machine creates Class objects.
* This constructor is not used and prevents the default constructor being
* generated.
*/
private Class(ClassLoader loader) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
}
每次加载一个数据类,比如String,JVM在加载String类的时候,先读取String.class文件,然后创建一个Class类型实例与String.class关联起来,这样每个Class实例都保存了对应class的所有信息,比如name,package,field,method,super等等,因此只要得到了Class实例,就能够得到该实例对应class的所有信息,这种方法就是反射
由上面Class类里可以看到,其构造函数是private的,因此无法直接通过构造函数来创建,想获取一个class的Class实例,有下面三种方法
//Type.class
Class cls = String.class;
//getClass()
String s = "Hello World";
Class cls = s.getClass();
//Class.forName()
Class cls = Class.forName("java.lang.String");
因此假如想获取变量对应类型或者类型的所有字段信息,可以先创建一个Class实例关联起来,然后通过反射,Class实例来访问class的所有信息即可
反射读取注解的API如下
Class.isAnnotationPresent(Class)
Field.isAnnotationPresent(Class)
Method.isAnnotationPresent(Class)
Constructor.isAnnotationPresent(Class)
Class.getAnnotation(Class)
Field.getAnnotation(Class)
Method.getAnnotation(Class)
Constructor.getAnnotation(Class)
getParameterAnnotations()
Java注解用得比较多,基本类,方法,字段甚至参数前面都可以看得到一种标签,样子跟Python装饰器一样
通常内置的注解有@Override,@Deprecated,@SuppressWarnings,这些都比较简单
这里首先有一些修饰其它注解的注解,称为元注解,通常元注解有
@Target
@Retention
@Repeatable
@Inherited
比如@Target元注解,就定义了Annotation能够应用到远吗哪些位置,具体包括
ElementType.TYPE:类和接口
ElementType.FIELD:字段
ElementType.METHOD:方法
ElementType.CONSTRUCTOR:构造方法
ElementType.PARAMETER:方法参数
比如@Retention元注解,定义了Annotation的生命周期,默认是CLASS
RetentionPolicy.SOURCE:编译期
RetentionPolicy.CLASS:class文件
RetentionPolicy.RUNTIME:运行期
因此正常定义一个Annotation注解可以考虑以下几步:
1:@interface定义
2:元注解来配置注解
3:定义注解参数和默认值
比如这里定义一个注解,表示非空
package com.maoxiaomeng.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: NotNull
* Author: lihui
* Date: 2018/6/18 下午4:57
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface NotNull {
}
在定义一个注解,表示取值范围
package com.maoxiaomeng.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Range
* Author: lihui
* Date: 2018/6/18 下午4:58
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {
int min() default 1;
int max() default 100;
}
在这里,这两个注解都添加了两个元注解,一个Field表示只能添加到字段前面,一个RUNTIME表示在运行时候才被读取
使用注解,这里name字段使用了@NotNull注解,检查是否非空,@Range注解传了一个参数,因此max不用默认值,检查age是否在1~20范围内,最后toString重写了Object里的toString方法,但是要注意的是,注解并不影响代码的逻辑,这些注解也不会自己去检查,因此还是要自己添加代码来检查,应用这些注解
package com.maoxiaomeng.annotation;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Person
* Author: lihui
* Date: 2018/6/18 下午4:59
*/
public class Person {
@NotNull
public String name;
@Range(max = 20)
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person(name = " + name + ", age = " + age + ")";
}
}
注解相关的方法了解一下
isAnnotationPresent:判断是否标注了指定注解
getAnnotation:获取指定注解,没有则返回null
getAnnotations:获取所有注解,包括继承自基类的,没有则返回长度为0的数组
getDeclaredAnnotations:获取自身显式标明的所有注解,没有则返回长度为0的数组
Main,通过注解最终来检查字段
package com.maoxiaomeng.annotation;
import java.lang.reflect.Field;
/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Main
* Author: lihui
* Date: 2018/6/18 下午5:02
*/
public class Main {
public static void main(String[] args) throws Exception {
Person p1 = new Person("Lucy", 25);
Person p2 = new Person(null, 15);
checkPerson(p1);
checkPerson(p2);
}
public static void checkPerson(Person p) throws Exception {
System.out.print("check " + p + ": ");
Class cls = Person.class;
for (Field f : cls.getFields()) {
checkField(f, p);
}
}
public static void checkField(Field f, Person p) throws Exception {
if (f.isAnnotationPresent(NotNull.class)) {
Object r = f.get(p);
if (r == null) {
System.out.println("Error, field " + f.getName() + " is null");
}
}
if (f.isAnnotationPresent(Range.class)) {
Range range = f.getAnnotation(Range.class);
int n = (Integer)f.get(p);
if (n < range.min() || n > range.max()) {
System.out.println("Error: field " + f.getName() + " is out of range");
}
}
}
}
首先定义两个Person对象,初始化赋值,接着调用checkPerson方法,获取Person的Class类型实例,然后获取其所有的Field类型,遍历所有Field类型,调用checkField方法,如果发现有某一个Field标注了@NotNull注解,name调用get方法,获取p的f字段的值,假如为null,说明这个字段有误,标注的注解不满足要求;同样如果发现某一个Field标注了@Range注解,首先获取f字段的注解,获取p的f字段的值,假如值不在注解的范围区间内,说明有误,注解不满足要求,输出错误
以上就是在运行期间通过反射来读取RUNTIME类型的注解,这个元注解一定不能丢
网上有个不错的思维导图
有兴趣可以查看廖雪峰的网站
XMIND图来自:http://www.cnblogs.com/peida/archive/2013/04/26/3038503.html