注解
概念
注解与注释有异曲同工之妙,注释便于操作者理解代码,而注解是在此之上给Java代码加标识,同时传输参数,这样的设定就可以让其他的类通过反射机制获取到该类的注解及其信息,并对该类进行某些操作。
这便是框架的实现概述,换句话说框架的核心就是注解,框架通过注解对某块代码实现框架的附加功能。
类别
注解可以分为元注解和自定义注解(JDK或框架自带注解和纯自定义注解)
元注解是用于描述自定义注解的注解。
自定义注解是用于描述其他代码的注解。
元注解
关键字 | 含义 |
---|---|
@Target | 描述该注解应用的范围 |
@Retention | 描述该注解生效周期 |
@Documented | 在JavaDoc文档中能否显示该注解 |
@Inherited | 子类是否可以继承超类的注解 |
@Target使用范围
关键字 | 范围 |
---|---|
TYPE | 类、接口(包括注解类型)或枚举声明 |
FIELD | 字段声明(包括枚举常量) |
METHOD | 方法声明 |
PARAMETER | 形参声明 |
CONSTRUCTOR | 构造函数声明 |
LOCAL_VARIABLE | 局部变量声明 |
ANNOTATION_TYPE | 注解类型声明 |
PACKAGE | 包装声明 |
TYPE_PARAMETER | 类型参数声明 |
TYPE_USE | 类型的使用 |
MODULE | 模块声明 |
@Retention使用范围
关键字 | 范围 |
---|---|
SOURCE | 编译器将丢弃注解 |
CLASS | 注解将由编译器记录在类文件中,但在运行时不需要由 VM 保留。 这是默认行为 |
RUNTIME | 注解将被编译器记录在类文件中,并在运行时由 VM 保留,因此它们可以被反射读取 |
创建自定义注解格式
权限 @interface 注解名 {
参数类型 参数名() default 默认值
}
自定义注解实例
@Target(value = ElementType.CONSTRUCTOR)
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FieldParameter {
int id() default 0;
String name() default " ";
int age() default 0;
}
类的加载
该部分涉及一些JVM底层原理,若只是想知道反射如何使用则不必观看此位置。
Java面向对象的核心就是类,而类的加载关联着反射机制。所以为更好理解反射机制,应该先把类的底层原理有大概的理解。在Java体系中类生成两种文件:.java和.class,.java为源码文件给开发者使用,.class为字节码文件给JVM使用,也就是说.java和.class文件是一对一的关系。
Java使用内存的三个区域
类加载流程
加载
将class字节码文件加载到内存中,并将这些数据转换成方法区中的运行时数据(静态变量、静态代码块、常量池等),在堆中生成一个Class类对象代表这个类(反射原理),作为方法区类数据的访问入口,某类的Class对象只可以有一个。
链接
将Java类的二进制代码合并到JVM的运行状态之中。
- 验证
确保加载的类信息符合JVM规范,没有安全方面的问题。 - 准备
正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。注意此时的设置初始值为默认值,具体赋值在初始化阶段完成。 - 解析
虚拟机常量池内的符号引用替换为直接引用(地址引用)的过程。
初始化
初始化阶段是执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先初始化其父类。
- 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
初始化顺序
先父后子、先静后非静、先变量、代码块后构造。
初始化引用方式
主动引用(一定会初始化)
- new一个类的对象。
- 调用类的静态成员(除了final常量)和静态方法。
- 使用java.lang.reflect包的方法对类进行反射调用。
- 当虚拟机启动,java Hello,则一定会初始化Hello类。说白了就是先启动main方法所在的类。
- 当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类
被动引用
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。例如:通过子类引用父类的静态变量,不会导致子类初始化。
- 通过数组定义类引用,不会触发此类的初始化。
- 引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)。
类加载器
类缓存
标准的Java SE类加载器可以按要求查找类,一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过,JVM垃圾收集器可以回收这些Class对象。
类加载器的分类
-
引导类加载器(bootstrap class loader)
- 用来加载 Java 的核心库,是用C语言实现的,并不继承自 java.lang.ClassLoader。
- 加载扩展类和应用程序类加载器。并指定他们的父类加载器。
-
扩展类加载器(extensions class loader)
- 用来加载 Java 的扩展库,Java 虚拟机会提供一个扩展库目录,该类加载器在此目录里面查找并加载 Java类。
- 由sun.misc.Launcher$ExtClassLoader实现。
-
应用程序类加载器(application class loader)
- 根据 Java 应用的类路径(classpath,java.class.path 路径下的内容)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。
- 由sun.misc.Launcher$AppClassLoader实现。
-
自定义类加载器
- 开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
ClassLoader类
java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java类,即java.lang.Class类的一个实例。ClassLoader还负责加载 Java 应用所需的资源,如图像文件和配置文件等。
常用方法
方法名 | 含义 |
---|---|
getParent() | 返回该类加载器的父类加载器 |
loadClass(String name) | 加载名称为 name的类,返回的结果是java.lang.Class类的实例 |
findClass(String name) | 查找名称为 name的类,返回的结果是java.lang.Class类的实例 |
findLoadedClass(String name) | 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例 |
defineClass(String name, byte[] b, int off, int len) | 把字节数组 b中的内容转换成 Java 类,返回的结果是java.lang.Class类的实例。这个方法被声明为 final的 |
resolveClass(Class<?> c) | 链接指定的 Java 类。 |
反射
Java被称为静态语言,因为在运行前所有数据都被固定下来。但由于反射的引入,Java被赋予了一定的动态性,利用反射机制可以在运行期获取类的所有信息并可以直接操作其内部属性和方法。
反射的核心是Class对象,就是先前介绍的每个类对应的唯一一个Class对象,作为调用数据的接口。
普通流程与反射流程对比
获得Class对象方法
- 类.class
- 实例对象.getClass()
- Class.forName(“类名”)
- 子类.getSuperclass()
通过Class对象可以反射的东西
类型 | 方法 |
---|---|
Field字段 | getField、getFields、getDeclaredField、getDeclaredFields |
Method方法 | getMethod、getMethods、getDeclaredMethod、getDeclaredMethods |
Constructor构造器 | getConstructor、getConstructors、getDeclaredConstructor、getDeclaredConstructors |
Superclass父类Class对象 | getSuperclass |
Interface实现接口 | getInterfaces |
Annotation注解 | getAnnotation、getAnnotations、getDeclaredAnnotation、getDeclaredAnnotations、getAnnotationsByType |
获取这些类型的方法中含有Declared表示获取全部(忽视权限),没有表示仅获取Public权限的。
//元注解描述
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
@interface ConstructorAnnotation {
//参数
String value() default "I am ConstructorAnnotation";
}
//元注解描述
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@interface FieldMethodParameter {
//参数
String describe();
Class type() default void.class;
}
public class Record {
//字段注解并传递参数
@FieldMethodParameter(describe = "成员变量id", type = int.class)
int id;
@FieldMethodParameter(describe = "成员变量name", type = String.class)
String name;
//无参构造器加注解
@ConstructorAnnotation
public Record() {
}
//有参构造器加注解传参数
@ConstructorAnnotation(value = "有参构造器")
public Record(
@FieldMethodParameter(describe = "接收参数id", type = int.class)
int id,
@FieldMethodParameter(describe = "接收参数name", type = String.class)
String name
) {
this.id = id;
this.name = name;
}
//方法注解并传递参数,下方都一样
@FieldMethodParameter(describe = "获取成员变量id", type = int.class)
public int getId() {
return id;
}
@FieldMethodParameter(describe = "设置成员变量id")
public void setId(@FieldMethodParameter(describe = "接收参数id", type = int.class)
int id) {
this.id = id;
}
@FieldMethodParameter(describe = "获取成员变量name", type = String.class)
public String getName() {
return name;
}
@FieldMethodParameter(describe = "设置成员变量name")
public void setName(@FieldMethodParameter(describe = "接收参数name", type = String.class)
String name) {
this.name = name;
}
public static void main(String[] args) {
//新建实例对象
Record record = new Record();
//getClass()方法获得Record类的Class对象
Class<? extends Record> recordClass = record.getClass();
//得到所有构造器
Constructor<?>[] constructors = recordClass.getDeclaredConstructors();
//循环判断是否实现ConstructorAnnotation.class的注解
for (Constructor constructor :
constructors) {
if (constructor.isAnnotationPresent(ConstructorAnnotation.class)) {
//判断为真,得到该类型注解并强转类型
ConstructorAnnotation annotation = (ConstructorAnnotation) constructor.getAnnotation(ConstructorAnnotation.class);
System.out.println(annotation.value());
//获取接收参数的注解,得到一个二维数组,该二维数据每行存储一个接收参数和其注解
Annotation[][] parameterAnnotations = constructor.getParameterAnnotations();
for (int j = 0; j < parameterAnnotations.length; j++) {
//判断该行参数是否有注解
int length = parameterAnnotations[j].length;
if (length == 0) {
System.out.println("该构造器接收参数无注解");
} else {
for (int k = 0; k < length; k++) {
//获取每个注解并输出
FieldMethodParameter pa = (FieldMethodParameter) parameterAnnotations[j][k];
System.out.println(pa.describe());
System.out.println(pa.type());
}
}
}
}
}
System.out.println("--------------------------------------------------------------");
//得到所有成员变量
Field[] declaredFields = recordClass.getDeclaredFields();
for (Field field :
declaredFields) {
//循环判断是否实现FieldMethodParameter.class的注解
if (field.isAnnotationPresent(FieldMethodParameter.class)) {
//判断为真,得到该类型注解并强转类型输出参数
FieldMethodParameter annotation = field.getAnnotation(FieldMethodParameter.class);
System.out.println(annotation.describe());
System.out.println(annotation.type());
}
}
System.out.println("--------------------------------------------------------------");
//得到所有方法
Method[] declaredMethods = recordClass.getDeclaredMethods();
for (Method method :
declaredMethods) {
//循环判断是否实现FieldMethodParameter.class的注解
if (method.isAnnotationPresent(FieldMethodParameter.class)) {
//判断为真,得到该类型注解并强转类型输出参数
FieldMethodParameter annotation = method.getAnnotation(FieldMethodParameter.class);
System.out.println(annotation.describe());
System.out.println(annotation.type());
}
//获取接收参数的注解,得到一个二维数组,该二维数据每行存储一个接收参数和其注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int j = 0; j < parameterAnnotations.length; j++) {
//判断该行参数是否有注解
int length = parameterAnnotations[j].length;
if (length == 0) {
System.out.println("方法接收参数无注解");
System.out.println(method.getName());
} else {
for (int k = 0; k < length; k++) {
//获取每个注解并输出
FieldMethodParameter fieldMethodParameter = (FieldMethodParameter) parameterAnnotations[j][k];
System.out.println(fieldMethodParameter.describe());
System.out.println(fieldMethodParameter.type());
}
}
}
}
}
}
通过反射创建对象、修改字段变量、执行方法
创建对象
- Class对象.newInstance() 调用类的无参构造函数返回实例对象,若该类没有无参构造函数则异常。
- 该类的Constructor对象.newInstance(参数)调用类的有参构造函数并返回实例对象。
执行方法
- 先反射获取所有方法,然后调用invoke(对象,参数)执行。
设置字段
- 获取字段,调用set(对象, 参数)修改字段属性。
@Target(value = ElementType.CONSTRUCTOR)
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
@Inherited
@interface FieldParameter {
int id() default 0;
String name() default " ";
int age() default 0;
}
public class Table {
private int id;
private String name;
private int age;
public Table() {
}
@FieldParameter(id = 001, name = "赵四", age = 18)
public Table(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void helloT1() {
System.out.println("Hello I Am Table1");
}
private void helloT2() {
System.out.println("Hello I Am Table2");
}
@Override
public String toString() {
return "Table{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
public static void main(String[] args) throws Exception {
//利用 Table.class对象的newInstance()和构造器的newInstance()方法进行创建对象
Table t1 = null;
Table t2 = Table.class.newInstance();
//获取所有构造器
Constructor<?>[] declaredConstructors = Table.class.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
//判断是否实现FieldParameter.class注解
if (declaredConstructor.isAnnotationPresent(FieldParameter.class)) {
FieldParameter annotation1 = (FieldParameter) declaredConstructor.getAnnotation(FieldParameter.class);
//获取FieldParameter注解传递的参数进行创建对象
t1 = (Table) declaredConstructor.newInstance(annotation1.id(), annotation1.name(), annotation1.age());
}
}
//获取所有方法
Method[] methods = Table.class.getDeclaredMethods();
for (Method method : methods) {
//找到名为helloT1和helloT2的方法
if (method.getName().equals("helloT1")) {
//执行invoke(具有该方法的实例对象,接收参数)
method.invoke(t1, null);
} else if (method.getName().equals("helloT2")) {
method.invoke(t2, null);
}
}
//获取所有字段变量
Field[] declaredFields = Table.class.getDeclaredFields();
for (Field declaredField : declaredFields) {
//判断字段类型并分类型赋值
if (declaredField.getGenericType().getTypeName().equals("int")) {
//判断字段名称分别赋值
if (declaredField.getName().equals("id"))
//调用set(具有该字段的实例对象, 字段值)
declaredField.set(t2, 2);
if (declaredField.getName().equals("age"))
declaredField.set(t2, 12);
} else if (declaredField.getGenericType().getTypeName().equals("java.lang.String"))
declaredField.set(t2, "王五");
}
System.out.println(t1);
System.out.println(t2);
}
}
总结
注解是学习框架的核心,它可以配合反射完成系列任务。而反射是实现Java动态的核心,可以实现运行期改变数据、调用方法等。
-
Author
- 小葳宝贝最爱吃饭