Java 从源码到执行通常须要三个过程:html
和 C/C++
这类系统级编程语言相比,Java 多了生成字节码文件与翻译字节码文件这些中间步骤,这是 Java 实现“一次编译到处执行”的基础,也是反射和注解的底层基础。相同的字节码在不一样的平台下会被 JVM 翻译成不一样的机器指令,从而实现跨平台执行。java
Java 提供了一种机制,容许咱们在载入(建立)类对象时读取与修改对象中的属性,这种机制基于 JVM。程序员能够经过 Java 内置的一些方法使用 JVM 的这部分特性。这是 Java 反射和注解的原理。程序员
维基百科对计算机科学中的反射解释以下:数据库
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++hibernate
探视代码和对象类型不是反射。在运行时经过类型检查来作一些修改才是反射。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 对象中,Java 提供了读取这些信息的方法,例如 Class.getAnnotation(...)
。
综合上面的介绍可知:
举个简单的例子来讲明反射和注解的一些功能。假设咱们有一个 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; }
上面的代码中,咱们从注解中提取了数据并构造了 Dog 对象,按照传统的方法咱们通常使用构造函数。以 Hibernate 为例,在关联对象和数据库表的时候咱们须要使用注解 @Table(name = "table_name")
来指明当前类关联的表。类对象和数据库表本不应有任何的耦合关系,因此不该该在构造函数中指定关联数据库表名,使用注解能够实现对象和表的解耦。测试的时候能够当作这些注解信息不存在,由于使用 new 建立对象的时候默认不解析注解信息。
Spring 中的依赖注入机制所依赖的就是 Java 的反射与注解。咱们常常会在 Spring 代码中看到类被加上了 @Bean
这个注解,Spring 项目在启动时,Spring 会扫描合法的字节码文件并搜索全部类的 Class 对象,若是发现一个类的 Class 对象中包含 @Bean
注解信息,则会自动建立这个类的一个对象,其余没有 @Bean
相关注解的类不会在系统中建立对象,除非你手动 new 一个。
下面的例子源自 how2j,我截取了部分,感谢原做者,侵删。要想完整理解下面的例子,最好先了解一下 Hibernate,能够参考 how2j 中介绍 Hibernate 的第一小节hello hibernate。
package hibernate_annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // 定义实体注解,以标识使用当前注解的对象为实体对象 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyEntity { } // 下面注解用于指明须要映射的数据库表 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyTable { String name(); }
@MyEntity // 标识当前类为实体 @MyTable(name="hero_") // 指明映射的数据库表 public class Hero { private int id; private String name; private int damage; private int armor; ... }
Class<Hero> clazz = Hero.class; // 尝试读取实体注解以判断当前对象是不是数据库实体对象 MyEntity myEntity = (MyEntity) clazz.getAnnotation(MyEntity.class); if (null == myEntity) { System.out.println("Hero类不是实体类"); } else { System.out.println("Hero类是实体类"); // 从 MyTable 注解中提取须要关联的数据库表 MyTable myTable= (MyTable) clazz.getAnnotation(MyTable.class); String tableName = myTable.name(); System.out.println("其对应的表名是:" + tableName); ... // 关联数据库表和实体对象的代码 }
在上面的例子中,咱们从注解中提取了信息并使用修改了原始对象属性。