Java反射和注解

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

发表回复