最近在学习Spring框架,分析源码时经常会用到反射和注解机制,这里对注解和反射做一个总结
1. 注解
首先,注解!=注释,注释是用文字描述程序,是给程序员看的,而注解是给计算机看的,它是在JDK1.5以后引入的一个特性。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明。
1.1 作用分类
- 编写文档:通过代码里标识的注解生成文档(平时看到的api文档就是通过javadoc命令将方法中的注解抽取而来)
- 代码分析:通过代码里标识的注解对代码进行分析(使用反射)
- 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查(@Override)
1.2 JDK中预定义的一些注解
- @Override:检测被该注解标注的方法是否是继承自父类(接口)的
- @Deprecated:该注解标注的内容表示已过时
- @SuppressWarnings:压制警告,一般传递参数all @SuppressWarnings(“all”)
1.3 自定义注解
格式
元注解
public @interface 注解名称{
属性列表;
}
本质
注解本质上就是一个接口,该接口默认继承Annotation接口
- public interface MyAnno extends java.lang.annotation.Annotation {}
属性
虽然称之为属性,但其实是接口中的抽象方法。
它有以下几种要求:
- 属性的返回值类型:
- 基本数据类型
- String
- 枚举
- 注解
- 以上类型的数组
- 定义了属性,在使用时需要给属性赋值
- 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
- 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
- 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
元注解
元注解即描述注解的注解
- @Target:描述注解能够作用的位置,该属性返回一个枚举类的数组,可以填写的值为
- TYPE:可以作用于类上
- METHOD:可以作用于方法上
- FIELD:可以作用于成员变量上
- @Retention:描述注解被保留的阶段
- @Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
- @Documented:描述注解是否被抽取到api文档中(即被该注解描述的注解名是否会出现在api文档中)
- @Inherited:描述注解是否被子类继承
2. 反射
框架是指半成品软件,可以在框架的基础上进行软件开发,简化编码过程;反射是将类的各个组成部分封装为其他对象,称之为反射机制。使用反射有以下好处:
- 可以在程序运行时,分析类的能力
- 在运行时使用反射分析对象
- 利用Method对象,实现类型程序指针的用途
Class类对象其实是类的字节码文件的对象,每个Class类对象中都会分为成员变量部分、构造方法部分以及成员方法部分,这几部分都被封装成了对象。
举一例子说明反射的应用,在IDE中,我们输入一个对象,会出现这个对象各个方法的列表,这其实就是使用了反射机制。
2.1 获取Class对象的方式
- Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象
- 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
- 类名.class:通过类名的属性class获取
- 多用于参数的传递
- 对象.getClass():getClass()方法在Object类中定义着。
- 多用于对象的获取字节码的方式
- 结论: 同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
2.2 Class对象功能
- 获取成员变量们
-
Field[] getFields() :获取所有public修饰的成员变量
-
Field getField(String name) 获取指定名称的 public修饰的成员变量
-
Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
-
Field getDeclaredField(String name)
-
- 获取构造方法们
-
Constructor[] getConstructors()
-
Constructor getConstructor(类… parameterTypes)
-
Constructor getDeclaredConstructor(类… parameterTypes)
-
Constructor[] getDeclaredConstructors()
-
- 获取成员方法们:
-
Method[] getMethods()
-
Method getMethod(String name, 类… parameterTypes)
-
Method[] getDeclaredMethods()
-
Method getDeclaredMethod(String name, 类… parameterTypes)
-
- 获取全类名
- String getName()
2.3 Field:成员变量
-
设置值 void set(Object obj, Object value)
-
获取值 get(Object obj)
-
忽略访问权限修饰符的安全检查 setAccessible(true) 暴力反射
Constructor:构造方法
- 创建对象: T newInstance(Object… initargs)
2.5 Method:方法对象
-
执行方法: Object invoke(Object obj, Object… args)
-
获取方法名称:String getName
2.6 使用反射获得注解
通过第一章的介绍,我们了解到注解的实质是一个接口,而这个接口中可以编写一些“属性”(其实就是接口的方法,只不过在注解这个语境下称谓属性)
//注解
public @interface Pro{
String className();
String methodName();
}
我们知道通过反射可以在运行时候分析类的能力,那能否使用反射得到加载类上的注解呢?答案是肯定的,如下面这段代码所示,使用Class类中的getAnnotation()方法得到加在该类上的“注解”。然后调用注解的“属性”,获得“赋给属性的值。
@Pro(className="cn.itcast.annotation.Demo1",methodName="show")
public class ReflectTest{
public static void main(String[] args) throws Exception{
//解析注解
//获取该类的字节码问价对象
Class<ReflectTest> reflectTestClass=ReflectTest.class;
//获取注解对象
//getAnnotation实际上在内存中生成了一个该注解接口的子类实现对象
Pro an=reflectTestClass.getAnnotation(Pro.class);
//调用注解对象中定义的抽象方法,获取返回值
String className=an.className();
}
}
此处你心头可能会有疑惑,注解本质上是一个接口,那么调用getAnnotation()莫非得到的是一个接口对象?接口是不能实例化为对象的,因此答案显然是否定的。调用getAnnotation的过程实际上是创建了注解接口的实现类,然后根据该类上注解属性的值相应的实现注解的“属性”(方法),详见下面这段代码
//getAnnotation在内存中创建的接口的子类对象
public class ProImpl implements Pro{
public String className(){
return "cn.itcast.annotation.Demo1";
}
public String methodName(){
return "show";
}
}
总结起来,注解的使用步骤
-
利用反射,获取注解修饰的对象(Class, Method, Field)
-
获取指定的注解
-
利用getAnnotation返回的对象,调用注解中的抽象方法获取配置的属性值