后面要学习的SSM、SpringBoot等框架的底层实现机制都是注解与反射。注解与反射是是以后学好框架的基础,十分重要。

1、注解(Annotation)

1.1、什么是注解

  • 注解是从JDK5.0开始引入的新技术(2022年JDK已经更新到18了)。
  • 与注释(Comment)类似,注解不是程序本身,但注解(Annotation)可以对程序作出解释,可以被其它程序(如:编译器等)通过反射的方式读取。
  • 注解必须按照它定义时规定的格式使用,否则会报错。

(1)注解的格式:

格式一:

1
@注解名

格式二:

1
@注解名(参数值)

注解以@注解名的形式在代码中存在,有时也需要添加一些参数值。如:@SuppressWarnings(value="unchecked")

(2)注解的用处

注解可以附加在package(包)、class(类)、method(方法)、field(属性)等前面,相当于给它们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问。

1.2、内置注解

Java语言内部定义了许多内置注解。这里仅介绍3个常用的内置注解:

  • @Override【重写】:定义在java.lang.Override中,只适用于修饰方法,表示一个方法将重写父类中的另一个方法。
  • @Deprecated【废弃】:定义在java.lang.Deprecated中,可以用于修饰方法、属性和类,表示不鼓励程序员使用这样的元素(但仍然是可以使用的),通常是因为它很危险或者存在更好的选择。
  • @SuppressWarings【镇压警告】:定义在java.lang.SuppressWarings中,用来抑制编译时的警告信息。与前面两个注解有所不同,我们需要添加一个参数才能正确使用该注解,这些参数在源码中已经被定义好了,我们只需要选择性的使用即可。如:
    • @SuppressWarings("all")
    • @SuppressWarings("unchecked")
    • @SuppressWarings(value={"unchecked","deprecation"})
    • ……

1.3、元注解(meta-annotation)

元注解,即:解释其它注解的注解。

  • Java语言在java.lang.annotation中定义了4个标准的元注解(meta-annotation)类型。

(1)@Target

Target

  • 用于描述注解的使用范围,即:被描述的注解可以用在什么地方。注解的使用范围取决于@Target的属性ElementType的取值。
取值 使用范围
@Target(ElementType.TYPE) 接口、类、枚举、注解
@Target(ElementType.FIELD) 字段、枚举的常量
@Target(ElementType.METHOD) 方法
@Target(ElementType.PARAMETER) 参数
@Target(ElementType.CONSTRUCTOR) 构造函数
@Target(ElementType.LOCAL_VARIABLE) 局部变量
@Target(ElementType.ANNOTATION_TYPE) 注解
@Target(ElementType.PACKAGE)
@Target(ElementType.TYPE_PARAMETER) 类型参数(@since 1.8)
@Target(ElementType.TYPE_USE) 用户类型(@since 1.8)

(2)@Retention

Retention

  • 用于描述注解的生命周期,生命周期的长短取决于@Retention的属性RetentionPolicy的取值。
取值 描述 作用范围 使用场景
RetentionPolicy.SOURCE 表示注解只保留在源文件,当java文件编译成class文件,就会消失。 源文件 只是做一些检查性的操作,如 :@Override 和 @SuppressWarnings。
RetentionPolicy.CLASS 表示注解被保留到class文件,当jvm加载class文件时候被遗弃,这是默认的生命周期。 class文件(默认) 要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife)
RetentionPolicy.RUNTIME 表示注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。 运行时也存在 需要在运行时去动态获取注解信息

注:

  • 上述三种类型生命周期:SOURCE < CLASS < RUNTIME。
  • 我们一般都需要在程序运行时去动态获取注解信息,所以我们一般将@Retention的值设置为:RUNTIME

(3)@Documented

Documented

表示该注解将被生成在javadoc(文档注释)中。

(4)@Inherited

Inherited

表示子类可以继承父类中的该注解。

1.4、自定义注解

格式:

1
2
3
4
5
6
7
8
@Documented
@Retention(生命周期)
@Target(使用范围)
public @interface 注解名称{
//注解的参数列表
参数类型 参数名称() [default 参数默认值];
……
}

注:

  • 注解定义中的每一个方法实际上是声明了一个配置参数
  • 方法的名称就是参数的名称;
  • 返回值类型就是参数的类型;返回值类型只能是基本数据类型、Class、String、Enum。
  • 可以通过default来声明参数的默认值,我们经常在定义注解参数时使用空字符串、0作为默认值。
  • 如果只有一个参数成员,一般参数名称为value,那么在使用该注解时可以省略参数名称value直接给该参数赋值。
  • 定义了参数的注解在使用时,我们必须要给没有默认值的参数赋值,否则会报错。
  • 如果一个注解有多个参数,那么再使用该注解时,给各参数的赋值是无序的。

例:

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
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class test {
/**
* 使用自定义注解
*/
@MyAnnotation(age = 18,name = "张三")
public static void main(String[] args) {
System.out.println("自定义注解使用成功!");
}
}

/**
* 自定义注解
*/
@Retention(RetentionPolicy.RUNTIME) //运行时有效
@Target({ElementType.METHOD,ElementType.CONSTRUCTOR}) //适用于方法和构造函数
@interface MyAnnotation{
//注解的参数
String name() default "";//默认值为空
int age();
int id() default -1;//默认值为-1,代表不存在
String[] schools() default {"西北工业大学","华中科技大学"};
}

1.5、注解的运行原理:反射+动态代理(重点)

注解的本质是:一个继承了Annotation的特殊接口。其具体实现类是java运行时,通过动态代理机制生成的动态代理类。

  • 编译器可以通过反射的方式读取注解

  • 当编译器通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1,进而可以通过代理对象$Proxy1调用注解(接口)中的方法。

  • 通过代理对象$Proxy1调用注解(接口)中的方法,会最终会调用AnnotationInvocationHandlerinvoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues 的来源是Java常量池。

2、反射(Reflection)

2.1、Java反射机制概述

2.1.1、静态语言 VS 动态语言

(1)动态语言(弱类型语言)

  • 动态语言是在运行时确定数据类型与结构的语言。变量使用之前不需要类型声明,通常变量的类型是被赋值的那个值的类型(如:JavaScript中的var变量)。
1
2
var a = 1;
var b = "字符串";
  • 常见的动态语言:Object-C、C#、JavaScript、PHP、Python等。

  • 优点: 给实际的编码带来了很大的灵活性,我们只关注对象的行为,而不关注对象本身。

  • 缺点: 代码运行期间有可能会发生与类型相关的错误。

(2)静态语言

  • 静态语言是在编译时确定变量的数据类型,运行期间不可以改变其结构的语言。即运行前可确定的语言。

    多数静态类型语言要求在使用变量之前必须声明数据类型。

  • 常见的静态语言:Java、C、C++等。

  • 优点:

    • 1.避免程序运行时发生变量类型相关的错误;

    • 2.先前明确了变量的类型,编译器可以针对这些信息对程序做出一些优化,从而提高程序执行的速度。

  • 缺点:

    • 1.写代码的时候,需要格外注意变量的类型;

    • 2.过多的类型声明会增加更多的代码。

注: Java 是静态语言,但是它具有一定的动态性。虽然Java不是动态语言,但是可以称之为“准动态语言”。即Java具有一定的动态性,我们可以通过Java的反射机制获得类似动态语言的特性。Java的动态性让编程更加灵活。

2.1.2、什么是反射

**反射(Reflection)**主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。(就像照镜子反射一样)

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

2.1.3、反射的基本原理

反射(Reflection)是Java被视为动态语言的关键。反射机制运行程序在执行过程中借助Reflection API取得任何类的内部信息(包括:类名、类的属性、类的成员变量、类的方法等),并能直接操作任意对象的内部属性和方法。

**注:**即使是private修饰的成员变量和方法,我们也可以通过反射机制直接获取到这些私有成员。

**类加载完成后,会在堆内存的方法区中产生一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了类的完整的结构信息。**我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子可以看到类的结构。所以,我们形象地称之为“反射”。

通过正常方式和反射反射获取实例的过程如下图所示。

正常与反射方式获取实例

2.1.4、反射的作用

Java反射机制提供的功能:

  • 在程序运行时判断任意一个对象所属的类;

  • 在程序运行时构造任意一个类的对象;

  • 在程序运行时判断任意一个类所具有的成员变量和方法;

  • 在程序运行时获取泛型信息;

  • 在程序运行时调用任意一个对象的成员变量和方法;

  • 在运行时处理注解;

  • 生成动态代理

    **注:**动态代理是一种机制,在以后学习AOP(面向切面编程)时会用到,框架中大量运用了动态代理。

  • ……

2.1.5、Java反射机制的优缺点

**(1)优点:**可以实现动态创建对象和编译,提高了代码的灵活性。

**(2)缺点:**会降低程序的性能。通过反射获取对象的速度比通过new的方式获取对象的速度慢几十倍。

2.2、理解Class类并获取Class实例

2.2.1、Class类

Java在Object类中定义了public final Class getClass()方法,该方法将被所有子类继承。该方法的返回值类型是一个Class类类型,此类是Java反射的源头。

实际上反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。

对象照镜子后可以得到的信息包括:某个类的属性、方法、构造器、实现的接口、父类等。对于每个类而言,JRE都为其保留了一个不变的Class类型的对象。无论这个类创建了多少个对象,但这个类对应Class对象是唯一的

注:

  • Class本身也是一个类;
  • Class对象只能由系统创建对象;
  • 在加载一个类时JVM中只会有一个Class实例;
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件;
  • 每个类的实例都会记得自己是由哪个Class实例所生成的;
  • 通过Class对象可以完整地得到一个类中的所有被加载的结构;
  • Class类是反射(Reflection)的根源,针对任何你想动态加载、运行的类,必须先获得相应的Class对象。

2.2.2、Class类的常用方法

方法名称 功能说明
static ClassforName(String name) 获取已知类名的Class对象
Object newInstance() 调用默认构造函数,创建Class对象的一个实例
getName() 获取该Class对象所表示的实体(类、接口、数组类或void)的名称
Class getSuperClass() 获取当前Class对象父类的Class对象
Class[] getInterfaces() 获取当前Class对象的接口
ClassLoader getClassLoaders() 获取该类的类加载器
Constructor[] getConstructors() 获取当前Class对象的构造器数组
Method getMethod(String name, Class… T) 获取该类的一个方法(Method)对象
Field[] getDeclaredFields() 获取该类的所有字段(包括私有成员)

2.2.3、如何获取Class类的实例

**(1)已知具体的类,通过类的class属性获取。**该方法最为安全可靠,程序性能最高。如:

1
Class 类名 = Person.class;

**(2)已知某个类的实例,调用该实例的getClass()方法获取Class对象。**如:

1
Class 类名 = person.getClass();

**(3)已知一个类的全类名,且该类在类路径下,则可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException异常。**如:

1
Class 类名 = Class.forName("com.atang.demo01.Student");

**(4)内置基本数据类型的包装类可以直接用类名.Type获取。**如:

1
Class 类名 = Integer.TYPE;	//int类型的包装类

(5)利用ClassLoader(类加载器)获取。

2.2.4、哪些类型可以有Class对象

具有Class对象的数据类型包括:

  • class:类类型。包括:外部类、成员(成员内部类、静态内部类)、局部内部类、匿名内部类。
  • interface:接口类型
  • []:数组类型
  • enum:枚举类型
  • annotation:注解类型(@interface)
  • primitive type:基本数据类型的包装类
  • void:空类型

例:

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
import java.lang.annotation.ElementType;
/**
* 所有具有Class对象的数据类型
*/
public class test {
public static void main(String[] args) {
Class c1 = Object.class; //类
Class c2 = Comparable.class; //接口
Class c3 = String[].class; //一维数组
Class c4 = int[][].class; //二维数组
Class c5 = Override.class; //注解
Class c6 = ElementType.class; //枚举
Class c7 = Integer.class; //基本数据类型的包装类
Class c8 = void.class; //空类型
Class c9 = Class.class; //Class类本身

System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
}
}

运行结果如下图所示:

具有Class对象的数据类型

2.3、类的加载与ClassLoader

2.3.1、类加载内存分析

(1)Java内存分析

Java的内存分为堆、栈和方法区。其中,方法区是一种特殊的堆。

Java内存分析

(2)类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。

类的加载过程
  • 加载:将类的class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象。

  • **链接:**将Java类的二进制代码合并到JVM运行状态之中的过程。

    • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题。
    • 准备:正式为类变量(static)分配内存,并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
    • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
  • 初始化:

    • 执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法执行时,编译器会自动收集类中所有类变量的赋值动作,并将其与静态代码块中的语句合并。(类构造器是构造类信息的,不是构造该类对象的构造器)。
    • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发器父类的初始化。
    • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。

2.3.2、什么时候会发生类的初始化

  • 类的主动引用(一定会发生类的初始化)

    • 当虚拟机启动时,先初始化main方法所在的类;
    • new一个类的对象时,该类会被初始化;
    • 调用类的静态成员(除final常量)和静态方法时,该类会被初始化;
    • 使用java.lang.reflect包的方法对类进行反射调用时,该类会被初始化;
    • 当初始化一个类时,如果其父类没有被初始化,则先会初始化其父类。
  • 类的被动引用(不会发生类的初始化)

    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类调用父类的静态变量,不会导致子类被初始化;

    • 通过数组定义类引用,不会触发此类的初始化。如:

      1
      Student[] array = new Student[5];	//此时Student类并不会被初始化
    • 引用常量不会触发此类的初始化。(常量在链接阶段就存入调用类的常量池中了)

2.3.3、类加载器(ClassLoader)

**(1)类加载器的作用:**将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。

**(2)类缓存:**标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM的垃圾回收机制可以回收这些Class对象。

类加载器

(3)类加载器的类型

JVM规范定义了如下类型的类加载器:

  • **引导类加载器:**用C++编写的,是JVM自带的类加载器,负责Java平台核心库(即:jre/lib目录下的rt.jar包(root)),用来装载核心类库。该加载器无法直接获取。
  • **扩展类加载器:**负责jre/lib/ext目录下的jar包或-D java.ext.dirs所指目录下的jar包装入工作库。
  • **系统类加载器:**负责java -classpath-D java.class.path所指目录下的类与jar包装入工作。System ClassLoader又称APPClassLoader,是最常用的加载器。

(4)类加载器的加载顺序

类加载器加载顺序

2.4、通过反射获取运行时类的完整结构

我们可以通过反射获取运行时类的完整结构,主要包括:**字段(Field)、方法(Method)、构造器(Constructor)、父类(Superclass)、接口(Interface)、注解(Annotation)**等。

如:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
* 获取运行时类的完整结构
*/
public class test {
public static void main(String[] args) throws ClassNotFoundException {
Class c1 = Class.forName("java.lang.String");//反射获取String类的Class对象

System.out.println("全类名:" + c1.getName());//获取全类名
System.out.println("类的简单名称:" + c1.getSimpleName());//获取类的简单名称

System.out.println("---------------------------");
//获取类的public属性
Field[] fields = c1.getFields();
for (Field field : fields ) {
System.out.println("类的public属性:" + field);
}

System.out.println("---------------------------");
//获取类的全部属性
Field[] declaredFields = c1.getDeclaredFields();
for (Field field : declaredFields ) {
System.out.println("类的全部属性:" + field);
}

System.out.println("---------------------------");
//获取本类及父类的public方法
Method[] methods = c1.getMethods();
for (Method method : methods ) {
System.out.println("本类及父类的public方法:" + method);
}

System.out.println("---------------------------");
//获取本类的全部方法
Method[] declaredMethods = c1.getDeclaredMethods();
for (Method method : declaredMethods ) {
System.out.println("本类的全部方法:" + method);
}

System.out.println("---------------------------");
//获取类的public构造器
Constructor[] constructors = c1.getConstructors();
for (Constructor constructor : constructors ) {
System.out.println("类的public构造器:" + constructor);
}

System.out.println("---------------------------");
//获取类的所有构造器
Constructor[] declaredConstructors = c1.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors ) {
System.out.println("类的所有构造器:" + declaredConstructor);
}
}
}

运行结果如下图所示(部分):

获取运行时类的完整结构

2.5、通过反射动态创建与使用对象

(1)如何通过反射动态创建对象

  • 当某个类含有无参构造函数时,我们可以直接通过该类的Class对象调用newInstance()方法来动态创建该类的对象。

  • 当某个类只有有参构造函数时,我们可以:

    ①先通过该类的Class对象调用getConstructor(Class<?>... parameterTypes)方法得到该类指定形参类型的构造器;

    ②然后再通过该有参构造器调用newInstance(形参列表)方法来动态创建该类的对象。

(2)如何通过反射动态调用类的方法(含私有)

主要步骤:

①先通过该类的Class对象动态创建该类的对象;

②然后再通过该类的Class对象调用getMethod(String name, Class<?>... parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型;

③最后,使用(激活函数)invoke(Object obj, Object... args)方法进行调用,并向方法中传递要设置的obj对象的参数信息。

通过反射动态调用类的方法

附1:激活函数invoke(Object obj, Object... args)方法

  • 返回值Object对应原方法的返回值。若原方法无返回值,则返回null
  • 若原方法为静态方法,则形参Object obj可为null
  • 若原方法形参列表为空,则Object[] argsnull
  • 若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。

附2:setAccessible(boolean flag)方法

  • MethodField、Constructor对象都有setAccessible(boolean flag)方法。
  • setAccessible的作用是启动和禁止访问安全检查的开关。
  • 若参数值为true,则指示反射的对象在使用时应该取消Java语言访问检查。
    • 可以提高反射的效率。
    • 使得原本无法访问的私有成员也可以访问。
  • 若参数值为false,则指示反射的对象在使用时应该实施Java语言访问检查(默认设置)。

(3)如何通过反射动态调用类的属性(含私有)

主要步骤:

①先通过该类的Class对象动态创建该类的对象;

②然后再通过该类的Class对象调用getField(String name)方法取得一个Field对象;

③最后,使用set(Object obj, Object value)方法进行调用。

**例:**先在src目录下自定义一个用户类,然后通过反射创建用户对象,并调用其属性和方法。

User.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
46
47
48
49
public class User {
private int id;//序号
private String name;//姓名
private int age;//年龄

//无参构造器
public User(){

}
//有参构造器
public User(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;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}

在主函数中通过反射动态创建对象,并通过该对象调用其方法和属性。

test.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
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* 通过反射动态创建与使用对象
*/
public class test {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class c1 = Class.forName("User");//通过反射获取User类的Class对象

//通过反射动态创建User对象(调用User类的无参构造器)
User user1 = (User)c1.newInstance();
System.out.println(user1);

//通过反射动态创建User对象(调用User类的有参构造器)
Constructor constructor = c1.getConstructor(int.class, String.class, int.class);
User user2 = (User)constructor.newInstance(1, "atang", 18);
System.out.println(user2);

//通过反射动态调用User类的setName()方法
User user3 = (User)c1.newInstance();//通过反射动态创建User对象
Method setName = c1.getDeclaredMethod("setName", String.class);//通过反射获取setName()方法
setName.invoke(user3,"张三");//激活setName()函数
System.out.println(user3.getName());

//通过反射动态调用User类的name属性
User user4 = (User)c1.newInstance();//通过反射动态创建User对象
Field name = c1.getDeclaredField("name");//通过反射获取name属性
name.setAccessible(true);//关闭Java的安全监测,才能动态调用User类的私有成员(字段和方法)
name.set(user4,"李四");//调用name属性
System.out.println(user4.getName());
}
}

程序运行结果如下:

通过反射动态创建与使用对象

2.6、性能对比分析

下面通过代码,对比分析通过普通方式调用方法、通过反射方式调用方法(不关闭安全监测)和通过反射方式调用方法(关闭安全监测)三者间所需要的时间。

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
46
47
48
49
50
51
52
53
54
55
56
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* 性能对比分析
*/
public class test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
test01();
test02();
test03();
}
//通过普通方式调用10亿次
public static void test01(){
User user = new User();

long startTime = System.currentTimeMillis();//开始时间
//调用10亿次
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();//结束时间
System.out.println("普通方式调用10亿次,用时:" + (endTime - startTime) + "ms");
}

//通过反射方式调用10亿次(不关闭安全监测)
public static void test02() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
Class c1 = Class.forName("User");
User user = (User)c1.newInstance();
Method getName = c1.getDeclaredMethod("getName", null);

long startTime = System.currentTimeMillis();//开始时间
//调用10亿次
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user,null);
}
long endTime = System.currentTimeMillis();//结束时间
System.out.println("通过反射方式调用10亿次(不关闭安全监测),用时:" + (endTime - startTime) + "ms");
}

//通过反射方式调用10亿次(关闭安全监测)
public static void test03() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
Class c2 = Class.forName("User");
User user = (User)c2.newInstance();
Method getName = c2.getDeclaredMethod("getName", null);
getName.setAccessible(true);//关闭安全监测

long startTime = System.currentTimeMillis();//开始时间
//调用10亿次
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user,null);
}
long endTime = System.currentTimeMillis();//结束时间
System.out.println("通过反射方式调用10亿次(关闭安全监测):" + (endTime - startTime) + "ms");
}
}

程序运行结果如下:

性能对比分析

结论:

  • 通过反射方式(不关闭安全监测)用时 > 通过反射方式(关闭安全监测)用时 > > 普通方式用时
  • 若平时经常使用反射,则建议关闭安全监测,这样可以提升程序的运行效率。

2.7、通过反射操作注解

下面我们将通过练习ORM,学习如何通过反射操作注解。

2.7.1、什么是ORM

ORM,即:Object-Relational Mapping(对象关系映射)。它的作用是在关系型数据库和业务实体对象之间作一个映射。

ORM

其中:

  • 类和表结构对应;
  • 属性和字段对应;
  • 对象和记录对应。

常用的 Java ORM 框架有 Hibernate 和 Mybatis。

2.7.2、利用注解与反射完成类和表结构的映射关系

例:利用注解与反射完成Student类和db_student表结构(字段分别为:db_id、db_age、db_name)的映射关系。

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* 反射操作注解
*/
public class test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("Student");//通过反射获取Student类的Class对象

//通过反射获取Student类的注解信息
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}

System.out.println("-----------------------");
//通过反射获取Student类的注解的value值
TableName tableName = (TableName) c1.getAnnotation(TableName.class);
String value = tableName.value();
System.out.println(value);

System.out.println("-----------------------");
//通过反射获取Student类中name属性的注解信息
Field name = c1.getDeclaredField("name");
Annotation[] annotations1 = name.getAnnotations();
for (Annotation annotation : annotations1) {
System.out.println(annotation);
}

System.out.println("-----------------------");
//通过反射获取Student类中name属性的注解的值
FieldName annotation = name.getAnnotation(FieldName.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}

//实体类
@TableName("db_student")//使用自定义类名的注解,将类名和数据库表名对应
class Student{
@FieldName(columnName = "db_id",type = "int",length = 10)//使用自定义属性的注解,将类的id属性和数据库的db_id字段对应
private int id;
@FieldName(columnName = "db_age",type = "int",length = 10)//使用自定义属性的注解,将类的age属性和数据库的db_age字段对应
private int age;
@FieldName(columnName = "db_name",type = "varchar",length = 3)//使用自定义属性的注解,将类的name属性和数据库的db_name字段对应
private String name;

//无参构造器
public Student() {
}

//有参构造器
public Student(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "Student{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}

//自定义类名的注解(用于类和表结构对应)
@Target(ElementType.TYPE)//适用于类
@Retention(RetentionPolicy.RUNTIME)//运行时有效
@interface TableName{
String value();//表名
}

//自定义属性的注解(用于属性和字段对应)
@Target(ElementType.FIELD)//适用于属性
@Retention(RetentionPolicy.RUNTIME)//运行时有效
@interface FieldName{
String columnName();//列名
String type();//数据类型
int length();//长度
}

(本讲完,系列博文持续更新中…… )

阿汤笔迹微信公众平台

关注**“阿汤笔迹”** 微信公众号,获取更多学习笔记。
原文地址:http://www.atangbiji.com/2022/09/08/annotationAndReflection
博主最新文章在个人博客 http://www.atangbiji.com/ 发布。