flyEn'blog

Java反射和注解

Java反射和注解

反射机制

Java反射机制指的是在程序运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任何一个对象,都能够调用它的任意一个方法和属性。这种 动态地获取信息 以及 动态调用对象的方法 的功能称为java的反射机制

反射机制可以让你在程序运行时,拿到任意一个类的属性和方法并调用它。

为什么要用反射机制?

首先,需要理解一下动态与静态的概念。

  • 静态编译:在编译时确定类型,绑定对象,即通过。
  • 动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的耦合性。

优点

可以实现动态创建对象和编译,体现出很大的灵活性。

缺点

对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

理解Class类和类的类型

首先理解一下Class类,它是反射实现的基础。

类是java.lang.Class类的实例对象,而Class是所有类的类。

对于普通的对象,我们一般都会这样创建和表示:

1
Code code = new Code();

既然所有的类都是Class的对象,那该如何表示呢,可不可以通过如下方式呢

1
Class c = new Class()

但是我们查看Class的源码时,是这样写的:

1
2
3
private  Class(ClassLoader loader) { 
classLoader = loader;
}

可以看到构造器是私有的,只有JVM可以创建Class的对象,因此不可以像普通类一样new一个Class对象,虽然我们不能new一个Class对象,但是却可以通过已有的类得到一个Clas对象,有三种方式,如下:

1
2
3
4
5
6
Class c1 = Code.class;
// 这说明任何一个类都有一个隐含的静态成员变量class,这种方式是通过获取类的静态成员变量class得到的
Class c2 = code1.getClass();
// code1是Code的一个对象,这种方式是通过一个类的对象的getClass()方法获得的
Class c3 = Class.forName("com.trigl.reflect.Code");
// 这种方法是Class类调用forName方法,通过一个类的全量限定名获得

这里,c1、c2、c3都是Class的对象,他们是完全一样的,而且有个学名,叫做Code的类类型(class type)。
这里就让人奇怪了,前面不是说Code是Class的对象吗,而c1、c2、c3也是Class的对象,那么Code和c1、c2、c3不就一样了吗?为什么还叫Code什么类类型?这里不要纠结于它们是否相同,只要理解类类型是干什么的就好了,顾名思义,类类型就是类的类型,也就是描述一个类是什么,都有哪些东西,所以我们可以通过类类型知道一个类的属性和方法,并且可以调用一个类的属性和方法,这就是反射的基础。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
//第一种:Class c1 = Code.class;
Class class1=ReflectDemo.class;
System.out.println(class1.getName());

//第二种:Class c2 = code1.getClass();
ReflectDemo demo2= new ReflectDemo();
Class c2 = demo2.getClass();
System.out.println(c2.getName());

//第三种:Class c3 = Class.forName("com.trigl.reflect.Code");
Class class3 = Class.forName("com.tengj.reflect.ReflectDemo");
System.out.println(class3.getName());
}
}

// 输出结果
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo

java反射的主要功能

  • 运行时构造一个类的对象
  • 运行时获取一个类所具有的成员变量和方法
  • 运行时调用任意一个对象的方法
  • 生成动态代理

反射的相关操作

前面我们说到了如何拿到一个类的Class信息,那用这个Class可以获取那些信息呢?

  • 获取/操作类的构造函数

  • 获取/操作类的成员变量

  • 获取/操作类的成员方法

获取类的构造函数

其实,类的构造函数是java.lang.reflect.Constructor类的对象,通过Class的下列方法可以获取构造函数对象:

1
2
3
4
5
6
7
8
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) //  获得该类所有的构造器,不包括其父类的构造器
public Constructor<T> getConstructor(Class<?>... parameterTypes) // 获得该类所有public构造器,包括父类

//具体
Constructor<?>[] allConstructors = class1.getDeclaredConstructors();//获取class对象的所有声明构造函数
Constructor<?>[] publicConstructors = class1.getConstructors();//获取class对象public构造函数
Constructor<?> constructor = class1.getDeclaredConstructor(String.class);//获取指定声明构造函数(局部变量是一个字符串类型的)
Constructor publicConstructor = class1.getConstructor(String.class);//获取指定声明的public构造函数

实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestConstructor {
public static void main(String[] args) throws Exception{
Class<?> personClass = Class.forName("com.catchu.me.reflect.Person");
//获取所有的构造函数,包括私有的,不包括父类的
Constructor<?>[] allConstructors = personClass.getDeclaredConstructors();
//获取所有公有的构造函数,包括父类的
Constructor<?>[] publicConstructors = personClass.getConstructors();
System.out.println("遍历之后的构造函数:");
for(Constructor c1 : allConstructors){
System.out.println(c1);
}

Constructor<?> c2 = personClass.getDeclaredConstructor(String.class);
c2.setAccessible(true); //设置是否可访问,因为该构造器是private的,所以要手动设置允许访问,如果构造器是public的就不用设置
Object person = c2.newInstance("刘俊重"); //使用反射创建Person类的对象,并传入参数
System.out.println(person.toString());
}
}

Person类如下,为测出效果包含一个私有构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Person {
private int age;
private String name;
public Person() {
}
private Person(String name){
this.name = name;
}
public Person(int age,String name){
this.age = age;
this.name = name;
}
//省略set/get方法
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}

测试结果:

1
2
3
4
5
遍历之后的构造函数:
public com.catchu.me.reflect.Person(int,java.lang.String)
private com.catchu.me.reflect.Person(java.lang.String)
public com.catchu.me.reflect.Person()
Person{age=0, name='刘俊重'}

由上面可以看到我们在获得某个类的Class类类型之后,可以通过反射包中的方法获取到这个类的构造函数,进而可以创建该类的对象。

获取类的成员变量

成员变量里包括:成员变量类型+成员变量名

其实,类的成员变量也是一个对象,它是java.lang.reflect.Field的一个对象,所以我们可以通过java.lang.reflect.Field里封装的方法来获取这些信息。

1
2
3
4
5
6
7
8
public Field getDeclaredField(String name) // 获得该类自身声明的所有变量,不包括其父类的变量
public Field getField(String name) // 获得该类自所有的public成员变量,包括其父类变量

//具体实现
Field[] allFields = class1.getDeclaredFields();//获取class对象的所有属性
Field[] publicFields = class1.getFields();//获取class对象的public属性
Field ageField = class1.getDeclaredField("age");//获取class指定属性
Field desField = class1.getField("des");//获取class指定的public属性

参数是成员变量所定义的名字。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class TestField {
public static void main(String[] args) throws Exception{
Class<Person> personClass = Person.class;
//获取所有的成员变量,包含私有的
Field[] allFields = personClass.getDeclaredFields();
//获取所有公有的成员变量,包含父类的
Field[] publicFields = personClass.getFields();
System.out.println("所有的成员变量:");
for(Field f : allFields){
System.out.println(f);
}

//获取某个变量的值
//创建对象的实例
Constructor<Person> c = personClass.getDeclaredConstructor(String.class);
c.setAccessible(true); //因为该构造函数时私有的,需要在这里设置成可访问的
Person person = c.newInstance("刘俊重");

//获取变量name对象
Field field = personClass.getDeclaredField("name");
field.setAccessible(true); //因为变量name是私有的,需要在这里设置成可访问的
//注意对比下面这两行,官方对field.get(Object obj)方法的解释是返回对象obj字段field的值
Object value = field.get(person);
//String name = person.getName();
System.out.println("获取的变量的值是:"+value);
}
}


// 输出结果
所有的成员变量:
private int com.catchu.me.reflect.Person.age
private java.lang.String com.catchu.me.reflect.Person.name
获取的变量的值是:刘俊重

这里要注意field.get(person)方法,我们根据对象获取属性的常规方法是通过:String name = person.getName(),反射中可以通过:字段.get(对象),这也是获取对象的某个字段,有点类似于invoke方法。

获取类的成员方法

万物皆对象,类的成员方法是java.lang.reflect.Method的对象,通过java.lang.Class类的以下方法可以获取到类的成员方法,通过方法类Method提供的一些方法,又可以调用获取到的成员方法。

1
2
3
4
5
6
7
8
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 得到该类所有的方法,不包括父类的 
public Method getMethod(String name, Class<?>... parameterTypes) // 得到该类所有的public方法,包括父类的

//具体使用
Method[] methods = class1.getDeclaredMethods();//获取class对象的所有声明方法
Method[] allMethods = class1.getMethods();//获取class对象的所有public方法 包括父类的方法
Method method = class1.getMethod("info", String.class);//返回此class1对应的public修饰的方法名是info的,包含一个String类型变量的方法
Method declaredMethod = class1.getDeclaredMethod("info", String.class);//返回此Class对象对应类的、带指定形参列表的方法

实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class TestMethod {
public static void main(String[] args) throws Exception {
Person person = new Person();
Class<? extends Person> personClass = person.getClass();
Method[] allMethods = personClass.getDeclaredMethods();
Method[] publicMethods = personClass.getMethods();
System.out.println("遍历所有的方法:");
for(Method m : allMethods){
System.out.println(m);
}

//下面是测试通过反射调用函数
//通过反射创建实例对象,默认调无参构造函数
Person person2 = personClass.newInstance();
//获取要调用的方法,要调用study方法,包含int和String参数,注意int和Integer在这有区别
Method method = personClass.getMethod("study", int.class, String.class);
Object o = method.invoke(person2, 18, "刘俊重");
}
}

// 输出结果
遍历所有的方法:
public java.lang.String com.catchu.me.reflect.Person.toString()
public java.lang.String com.catchu.me.reflect.Person.getName()
public void com.catchu.me.reflect.Person.setName(java.lang.String)
public void com.catchu.me.reflect.Person.study(int,java.lang.String)
public int com.catchu.me.reflect.Person.getAge()
public void com.catchu.me.reflect.Person.setAge(int)
我叫刘俊重,我今年18,我在学习反射

注意:Object o = method.invoke(person2, 18, “刘俊重”);就是调用person2对象的method方法,格式是:方法名.invoke(对象,参数),类似于获取成员变量值时的get方法。

由上面可以看出反射的强大:通过反射我们可以获取到类类型,通过Class类型我们可以获取到构造函数,进而实例化new出一个对象;通过反射我们可以获取到成员变量和成员方法,通过实例出的对象又可以获取到这些成员变量的值或调用成员方法。这才只是反射的一部分,通过反射我们还可以判断类,变量,方法,是否包含某些特定注解,还可以通过反射来动态代理去调用其它方法,跟注解和动态代理挂起勾会有无限的想象空间,比如spring框架,底层就是通过这些原理。下面在说几个反射常用的API,最后会介绍反射跟注解和动态代理的结合使用。

其他方法

1.注解中常用的方法

1
2
3
4
Annotation[] annotations = (Annotation[]) class1.getAnnotations();//获取class对象的所有注解 
Annotation annotation = (Annotation) class1.getAnnotation(Deprecated.class);//获取class对象指定注解
Type genericSuperclass = class1.getGenericSuperclass();//获取class对象的直接超类的
Type Type[] interfaceTypes = class1.getGenericInterfaces();//获取class对象的所有接口的type集合

2.获取Class对象其他信息的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
boolean isPrimitive = class1.isPrimitive();//判断是否是基础类型 
boolean isArray = class1.isArray();//判断是否是集合类
boolean isAnnotation = class1.isAnnotation();//判断是否是注解类
boolean isInterface = class1.isInterface();//判断是否是接口类
boolean isEnum = class1.isEnum();//判断是否是枚举类
boolean isAnonymousClass = class1.isAnonymousClass();//判断是否是匿名内部类
boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);//判断是否被某个注解类修饰
String className = class1.getName();//获取class名字 包含包名路径
Package aPackage = class1.getPackage();//获取class的包信息
String simpleName = class1.getSimpleName();//获取class类名
int modifiers = class1.getModifiers();//获取class访问权限
Class<?>[] declaredClasses = class1.getDeclaredClasses();//内部类
Class<?> declaringClass = class1.getDeclaringClass();//外部类
ClassLoader ClassLoader = class1.getClassLoader() 返回类加载器

getSuperclass():获取某类所有的父类
getInterfaces():获取某类所有实现的接口

通过反射了解集合泛型的本质

Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译到了运行期就无效了。

验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* 集合泛型的本质
* @description
* @author Trigl
* @date 2016年4月2日上午2:54:11
*/
public class GenericEssence {
public static void main(String[] args) {
List list1 = new ArrayList(); // 没有泛型
List<String> list2 = new ArrayList<String>(); // 有泛型


/*
* 1.首先观察正常添加元素方式,在编译器检查泛型,
* 这个时候如果list2添加int类型会报错
*/
list2.add("hello");
// list2.add(20); // 报错!list2有泛型限制,只能添加String,添加int报错
System.out.println("list2的长度是:" + list2.size()); // 此时list2长度为1


/*
* 2.然后通过反射添加元素方式,在运行期动态加载类,首先得到list1和list2
* 的类类型相同,然后再通过方法反射绕过编译器来调用add方法,看能否插入int
* 型的元素
*/
Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1 == c2); // 结果:true,说明类类型完全相同

// 验证:我们可以通过方法的反射来给list2添加元素,这样可以绕过编译检查
try {
Method m = c2.getMethod("add", Object.class); // 通过方法反射得到add方法
m.invoke(list2, 20); // 给list2添加一个int型的,上面显示在编译器是会报错的
System.out.println("list2的长度是:" + list2.size()); // 结果:2,说明list2长度增加了,并没有泛型检查
} catch (Exception e) {
e.printStackTrace();
}

/*
* 综上可以看出,在编译器的时候,泛型会限制集合内元素类型保持一致,但是编译器结束进入
* 运行期以后,泛型就不再起作用了,即使是不同类型的元素也可以插入集合。
*/
}
}

动态代理

代理的操作是通过java.lang.reflect.Proxy 类中实现的,通过ProxynewProxyInstance()方法可以创建一个代理对象,如下:

1
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

从中,可以看到需要三个参数,类加载器,接口和调用处理者。我们在上面已经能拿到Class类了,使用class.getClassLoader就可以获取类加载器,使用class.getgetInterfaces()可以获取所有的接口,那现在要写的不就是新建一个InvocationHandler对象了吗?事实上,我们动态代理的核心代码也就是在这里面写的。我上面说的模板,其实就是下面这几步:

  1. 书写代理类和代理方法,在代理方法中实现代理Proxy.newProxyInstance();

  2. 代理中需要的参数分别为:被代理的类的类加载器class.getClassLoader(),被代理类的所有实现接口new Class[] { Interface.class },句柄方法new InvocationHandler();

  3. 在句柄方法中重写invoke方法,invoke方法的输入有3个参数Object proxy(代理类对象), Method method(被代理类的方法),Object[] args(被代理类方法的传入参数),在这个方法中,我们可以定制化的写我们的业务;

  4. 获取代理类,强转成被代理的接口;

  5. 最后,我们可以像没被代理一样,调用接口的任何方法,方法被调用后,方法名和参数列表将被传入代理类的invoke方法中,进行新业务的逻辑流程。

    看下面的示例代码: 接口PersonInterface:

1
2
3
4
public interface PersonInterface {
void doSomething();
void saySomething();
}

接口的实现类:

1
2
3
4
5
6
7
8
9
10
public class PersonImpl implements PersonInterface {
@Override
public void doSomething() {
System.out.println("人类在做事");
}
@Override
public void saySomething() {
System.out.println("人类在说话");
}
}

代理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @author 刘俊重
*/
public class PersonProxy {
public static void main(String[] args) {
final PersonImpl person = new PersonImpl();
PersonInterface proxyPerson = (PersonInterface) Proxy.newProxyInstance(PersonImpl.class.getClassLoader(),
PersonImpl.class.getInterfaces(), new InvocationHandler() {
//在下面的invoke方法里面写我们的业务
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName()=="doSomething"){
person.doSomething();
System.out.println("通过常规方法调用了实现类");
}else{
method.invoke(person,args);
System.out.println("通过反射机制调用了实现类");
}
return null;
}
});
proxyPerson.doSomething();
proxyPerson.saySomething();
// 执行结果:
// 人类在做事
// 通过常规方法调用了实现类
// 人类在说话
// 通过反射机制调用了实现类

}
}

在我们通过proxyPerson.doSomething()调用的时候,其实不是立马进入实现类的doSomething方法,而是带着方法名,参数进入到了我们的代理方法invoke里面,在这里面我进行了一次判断,如果等于”doSomething”就使用常规方法调用,否则使用反射的方法调用。这样看似还是平时的调用,但是每次执行都要走我们的代理方法里面,我们可以在这里面做些“手脚”,加入我们的业务处理

注解

概念

  • 注解即元数据,就是源代码的元数据。

元数据是指用来描述数据的数据,更通俗一点,就是描述代码间关系,或者代码与其他资源(例如数据库表)之间内在联系的数据。

  • 注解在代码中添加信息提供了一种形式化的方法,可以在后续中更方便地使用这些数据。
  • Annotation是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的一种工具。

作用

  • 生成文档
  • 跟踪代码依赖性,实现替代配置文件功能,减少配置。如Spring中的一些注解。
  • 在编译时进行格式检验,如@Override等
  • 每当你创建描述符性质的类或者接口时,一旦其中包含重复性的工作,就可以考虑使用注解来简化与自动化该过程。

java注解

什么是java注解? 在java语法中,使用@符号作为开头,并在@后面紧跟注解名。被运用于类,接口,方法和字段之上,java中的注解包是java.lang.annotation,例如:

1
2
3
4
@Override
void myMethod() {
......
}

这其中@Override就是注解。这个注解的作用也就是告诉编译器,myMethod()方法覆盖了父类中的myMethod()方法。

java中内置的注解

  1. @Override:表示当前的方法定义将覆盖超类中的方法,如果出现错误,编译器就会报错。
  2. @Deprecated:如果使用此注解,编译器会出现警告信息。
  3. @SuppressWarnings:忽略编译器的警告信息。

java注解类反编译

java中的类、接口、枚举、注解都可以看做是类类型。如果利用JAD反编译工具来看@interface被转换成什么:

1
2
3
4
5
6
7
8
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Foo{
String[] value();
boolean bar();
}

查看反编译代码可以看出:

  • 自定义的注解类Foo被转换成接口Foo,并且继承Annotation接口
  • 原来自定义接口中的value()和bar()被转换成抽象方法
1
2
3
4
5
6
7
8
9
import java.lang.annotation.Annotation;

public interface Foo
extends Annotation
{
public abstract String[] value();

public abstract boolean bar();
}

注解通常和反射配合使用,而且既然自定义的注解最终被转换成接口,注解中的属性被转换成接口中的抽象方法,那么通过反射之后拿到接口实例,在通过接口实例自然能够调用对应的抽象方法:

1
2
3
4
5
6
7
8
9
10
import java.util.Arrays;

@Foo(value={"sherman", "decompiler"}, bar=true)
public class Demo{
public static void main(String[] args) {
Foo foo = Demo.class.getAnnotation(Foo.class);
System.out.println(Arrays.toString(foo.value())); // [sherman, decompiler]
System.out.println(foo.bar()); // true
}
}

元注解:

元注解的作用就是负责注解其他注解(自定义注解的时候用到的)。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。

Java5.0定义的4个元注解:@Target@Retention@Documented@Inherited

java8加了两个新注解

@Target

@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

作用:用于描述注解的使用范围。(即:被描述的注解可以用在什么地方

取值(ElementType)有:

类型 用途
CONSTRUCTOR 用于描述构造器
FIELD 用于描述域
LOCAL_VARIABLE 用于描述局部变量
METHOD 用于描述方法
PACKAGE 用于描述包
PARAMETER 用于描述参数
TYPE 用于描述类、接口(包括注解类型) 或enum声明

比如定义下面一个注解,它就只能用在方法上,因为已经限定了它是方法级别的注解,如果用在类或者其它上面,编译阶段就会报错

1
2
3
@Target({ElementType.METHOD})
public @interface MyMethodAnnotation {
}

@Retention

@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效

取值有:

类型 用途 说明
SOURCE 在源文件中有效(即源文件保留) 仅出现在源代码中,而被编译器丢弃
CLASS 在class文件中有效(即class文件保留) 被编译在class文件中
RUNTIME 在运行时有效(即运行时保留) 编译在class文件中

示例:

1
2
3
4
5
@Target({ElementType.TYPE})  //用在描述类、接口或enum
@Retention(RetentionPolicy.RUNTIME) //运行时有效
public @interface MyClassAnnotation {
String value(); //这个MyClassAnnotation注解有个value属性,将来可以设置/获取值
}

@Documented

@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

作用:是在生成javadoc文档的时候将该Annotation也写入到文档中。即将注解写入在javadoc中

1
2
3
java.lang.annotation.Documented
@Documented
public @interface MyCustomAnnotation { //Annotation body}

@Inherited

是一个标记注解,阐述了某个被标注的类型是被继承的,使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类,@Inherited annotation类型是被标注过的class的子类所继承。类并不从实现的接口继承annotation,方法不从它所重载的方法继承annotation,当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

作用:允许子类继承父类中的注解。

示例,这里的MyParentClass 使用的注解标注了@Inherited,所以子类可以继承这个注解信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
java.lang.annotation.Inherited
@Inherited
public @interface MyCustomAnnotation {
}

@MyCustomAnnotation
public class MyParentClass {
...
}

public class MyChildClass extends MyParentClass {
...
}

JDK提供的几个基本注解:

  1. @SuppressWarnings:阻止编译器发出某些警告信息。
  2. @Deprecated:标记某个过时的类或方法
  3. @Override:用在方法面前,用来标识该方法是重写父类的某个方法。

自定义注解

格式:

1
2
3
public @interface 注解名{
定义体
}

注解参数的可支持数据类型

  • 所有基本数据类型(int,float,double,boolean,byte,char,long,short)
  • String类型
  • Class类型
  • enum类型
  • Annotation类型
  • 以上所有类型的数组

规则

  • 修饰符只能是public 或默认(default)
  • 参数成员只能用基本类型byte,short,int,long,float,double,boolean八种基本类型和String,Enum,Class,annotations及这些类型的数组
  • 如果只有一个参数成员,最好将名称设为value
  • 注解元素必须有确定的值,可以在注解中定义默认值,也可以使用注解时指定,非基本类型的值不可为null,常使用空字符串或0作默认值
  • 在表现一个元素存在或缺失的状态时,定义一下特殊值来表示,如空字符串或负值
1
2
3
4
5
6
7
8
@Target(ElementType.FIELD)
@Retention(value=RetentionPolicy.RUNTIME)
@Documented
public @interface MyFieldAnnotation {
int id() default 0;

String name() default "";
}

定义了一个用在字段上的,运行时有效的名为MyFieldAnnotation的注解,它有两个属性,int类型的id(id后面记得带括号)默认值是0,还有一个String类型的name,默认值是””。

注解怎么使用?

在上面我们已经知道了怎么自定义一个注解了,但是光定义没用啊,重要的是我要使用它,使用的方法也很简单,最上面讲反射的时候也提到过几个这样的方法了,比如:class1.isAnnotation(),其实他们统统是java.lang.reflect包下的AnnotatedElement接口里面的方法,这个接口主要有以下几个实现类:

  • Class:类定义
  • Constructor:构造器定义
  • Field:累的成员变量定义
  • Method:类的方法定义
  • Package:类的包定义

java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。 AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:

  • 方法1: T getAnnotation(Class annotationClass): 返回程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
  • 方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
  • 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
  • 方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

    总结:定义注解用的是java.lang.annotation.Annotation,操作注解是用java.lang.reflect.AnnotatedElement

测试代码CustomClassAnnotation如下:

1
2
3
4
5
6
7
8
9
10
/**
* @author 刘俊重
* @Description 自定义类注解
*/
@Target(ElementType.TYPE) //作用在类,枚举或接口上
@Retention(RetentionPolicy.RUNTIME) //运行时有效
@Documented //文档可见
public @interface CustomClassAnnotation {
String value(); //获取注解名称
}

FruitName类如下:

1
2
3
4
5
6
7
8
9
10
/**
* @author 刘俊重
* @Description 字段注解(字符串类型的)
*/
@Target(ElementType.FIELD) //用在字段上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String name() default "";
}

FruitColor类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author 刘俊重
* @Description 字段注解(枚举类型的)
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
//颜色枚举
enum Color{BLUE,RED,GREEN};

//颜色属性
Color color() default Color.RED;
}

Fruit实体类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author 刘俊重
* @Description Fruit实体类
*/
@CustomClassAnnotation(value="fruit")
public class Fruit{

@FruitName(name="apple")
private String name;

@FruitColor(color= FruitColor.Color.RED)
private String color;

}

测试类TestAnnotation如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* @author 刘俊重
* @Description 测试类
*/
public class TestAnnotation {
public static void main(String[] args) {
Class<Fruit> clazz = Fruit.class; //反射获取Class对象
CustomClassAnnotation annotation = clazz.getAnnotation(CustomClassAnnotation.class); //拿到Fruit类的注解
if(null!=annotation && "fruit".equals(annotation.value())){
System.out.println("Fruit类的注解名是======"+annotation.value());
//获取所有的属性遍历,拿到每一个属性的值
Field[] allFields = clazz.getDeclaredFields();
for(Field field : allFields){
if(field.isAnnotationPresent(FruitName.class)){
//判断是否存在FruitName注解
FruitName fruitName = field.getAnnotation(FruitName.class);
System.out.println("水果名称====="+fruitName.name());
}
if(field.isAnnotationPresent(FruitColor.class)){
FruitColor fruitColor = field.getAnnotation(FruitColor.class);
System.out.println("水果颜色====="+fruitColor.color());
}
}
}else{
System.out.println("注解值不对,请检查");
}
}
}

总结

通过注解可以获取到类名,接口名,方法名,属性名,再搭配反射可以动态的生成对象,再搭配动态代理,去动态的去调用某些方法,这基本上也就是spring框架底层实现原理的一部分了。

本文参考:https://juejin.im/post/5a44c0ad518825455f2f96e5#heading-17。

Fork me on GitHub