反射与注解
Java 从源码到执行一般需要三个过程:
- 编译并生成字节码文件,即 class 文件或者 jar 包
- JVM 加载字节码文件并初始化运行环境,例如将字节码翻译成机器指令、初始化对象、加载依赖包等
- 执行 Java 程序
和 C/C++ 这类系统级编程语言相比,Java 多了生成字节码文件与翻译字节码文件这些中间步骤,这是 Java 实现“一次编译处处执行”的基础,也是反射和注解的底层基础。相同的字节码在不同的平台下会被 JVM 翻译成不同的机器指令,从而实现跨平台执行。
Java 提供了一种机制,允许我们在载入(创建)类对象时修改对象中的属性,这种机制基于 JVM:JVM 在将字节码翻译为机器指令的过程中可以修改对象属性的值,可以为对象添加其他方法等等。程序员可以通过 Java 内置的一些方法使用 JVM 的部分特性。
反射与类中的 Class 对象
维基百科对计算机科学中的反射解释如下:
In computer science, reflection is the ability of a process to examine, introspect, and modify its own structure and behavior.
在计算机科学中,反射是运行时查看与修改自身结构和行为的能力。
Java 中运行时可以通过反射修改属性和方法的访问限制(例如从 private 修改为 public )。
stackoverflow 上点赞较多的回答如下:
The ability to inspect the code in the system and see object types is not reflection, but rather Type Introspection. Reflection is then the ability to make modifications at runtime by making use of introspection. The distinction is necessary here as some languages support introspection, but do not support reflection. One such example is C++
探视代码和对象类型不是反射。在运行时通过类型检查来做一些修改才是反射。C++可以查看对象的类型(例如使用 typeid)但不能在运行时对对象做修改,故C++不支持反射。
上面两个解释中都强调了反射运行时修改的特点。
Java 是面向对象的语言,除了内置的 POD(Plain Old Data)类型,其他所有数据类型都是对象,而且这些对象中有着很多相同的方法,例如:equal、toString 等等。每一个 Java 类中都有一个 Class 对象 class(类似于静态成员变量),Class 对象保存了类本身的信息,例如类有多少属性,这些属性的类型是什么;还有就是类有哪些方法,这些方法的参数又是什么等等。Class 对象是 Java 反射的基础,只要提供一个类的 Class 对象我们就可以不用 new 而是使用 Java 提供的方法构造一个对应的类对象。假设我们已经有了一个 Dog 类,那么我们就可以使用下面的方式在运行时构造一个 Dog 对象:
Class pClass = Class.forName(Dog.class); // 获得 Dog 类的 Class 对象 Constructor c = pClass.getConstructor(); // 通过 Class 对象获得 Dog 类的构造函数 Dog xiaohei = (Dog) c.newInstance(); // 构造一个 Dog 对象小黑
注解与类中的 Class 对象
注解信息会保存在类的 Class 对象中,Java 提供了读取这些信息的方法,例如 Class.getAnnotation(...)。
综合上面的介绍可知:
- Java 可以通过 Class 对象获得一个类的详细信息
- 注解信息也保留在了 Class 对象中
- Java 提供了在生成类对象时修改对象属性方法的机制
举个简单的例子来说明反射和注解的一些功能。假设我们有一个 Dog 类,Dog 类中有 name、gender、color 等属性,这些属性在 Dog 的 Class 对象中是有记录的。现在我们有了一个 DogInit 注解,这个注解中也有若干个属性,例如 name、gender、color等。使用 Java 提供的注解语法将 DogInit 和 Dog 关联起来:
@DogInit(name="xiaohei", gender="boy", color="black")
class Dog{
public static Dog getDefaultDog()
{
DogInit dogInit = Dog.class.getAnnotation(DogInit.class); // 通过 Class 对象获取注解信息
Dog dog; // 通过反射而非构造函数的形式初始化了一个 Dog 对象
dog.name = dogInit.getName();
dog.gender = dogInit.getGender();
dog.color = dogInit.getColor();
return dog;
}
...
private name;
private gender;
private color;
}
Spring 中的依赖注入机制所依赖的就是 Java 的反射与注解。我们经常会在 Spring 代码中看到类被加上了 @Bean 这个注解,Spring 项目在启动时,Spring 会扫描合法的字节码文件并搜索所有类的 Class 对象,如果发现一个类的 Class 对象中包含 @Bean 注解信息,则会自动创建这个类的一个对象,其他没有 @Bean 相关注解的类不会在系统中创建对象,除非你手动 new 一个。