1、类加载过程
类加载的完整过程如下:
-
在编译时,Java 编译器编译好 .java 文件之后,在磁盘中产生 .class 文件。.class 文件是二进制文件,内容是只有 JVM 能够识别的机器码。
-
JVM 中的类加载器读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息。类加载器会根据类的全限定名来获取此类的二进制字节流;然后,将字节流所代表的静态存储结构转化为方法区的运行时数据结构;接着,在内存中生成代表这个类的 java.lang.Class 对象。
-
加载结束后,JVM 开始进行连接阶段(包含验证、准备、初始化)。经过这一系列操作,类的变量会被初始化
2、类加载器
类加载器负责将.class文件加载到内存中,并为之生成对应的java.lang.Class文件。
当JVM启动时,会形成三个类加载器组成的初始类加载器层次结构:Bootstrap ClassLoader(根类加载器)、Extension ClassLoader(扩展类加载器)、System ClassLoader(系统类加载器)。
Bootstrap ClassLoader被称为引导类加载器,它负责加载Java的核心类。该类加载器非常特殊,他并不是java.lang.ClassLoader的子类,而是由JVM自身实现的,由C++实现
Extension ClassLoader被称为扩展类加载器,它负责加载JRE的扩展目录中JAR包的类。通过这种方式,就可为Java扩展核心类以外的功能,只要把自己开发的类打包成JAR文件,然后放入JAVA_HOME/jre/lib/ext路径即可。
System ClassLoader被称为系统(也称为应用)类加载器,负责在JVM启动加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以类加载器作为父加载器。
JVM中4种类加载器的层次结构(类加载器中的父子关系并不是类继承上的父子关系,这里的父子关系是类加载器实例之间的关系):用户类加载器->系统类加载器->扩展类加载器->根类加载器。
3、类加载机制
JVM的类加载机制主要有如下三种:
全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另一个类加载器来载入。
父类委托:先让父类加载器试图加载该Class,只有父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
缓存机制:缓存机制将会保证所有加载过的Class都被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存去不存在该Class对象时,系统才会读取该类对应你的二进制数据,并将其转换成Class对象,存入缓存区中,这就是为了修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。
反射
1、什么是反射?
反射(Reflection)是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性
2、反射的应用场景
-
开发通用框架 - 反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 JavaBean、Filter 等),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射——运行时动态加载需要加载的对象。
-
动态代理 - 在切面编程(AOP)中,需要拦截特定的方法,通常,会选择动态代理方式。这时,就需要反射技术来实现了。
-
注解 - 注解本身仅仅是起到标记作用,它需要利用反射机制,根据注解标记去调用注解解释器,执行行为。如果没有反射机制,注解并不比注释更有用。
-
可扩展性功能 - 应用程序可以通过使用完全限定名称创建可扩展性对象实例来使用外部的用户定义类。
3、反射的缺点
-
性能开销 - 由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能要比非反射操作的性能要差,应该在性能敏感的应用程序中频繁调用的代码段中避免。
-
破坏封装性 - 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
-
内部曝光 - 由于反射允许代码执行在非反射代码中非法的操作,例如访问私有字段和方法,所以反射的使用可能会导致意想不到的副作用,这可能会导致代码功能失常并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为。
4、Class对象
要想使用反射,首先需要获得待操作的类所对应的 Class 对象。Java 中,无论生成某个类的多少个对象,这些对象都会对应于同一个 Class 对象。这个 Class 对象是由 JVM 生成的,通过它能够获悉整个类的结构。所以,java.lang.Class 可以视为所有反射 API 的入口点。
反射的本质就是:在运行时,把 Java 类中的各种成分映射成一个个的 Java 对象。
举例来说,假如定义了以下代码:User user = new User();
步骤说明:
1、JVM 加载方法的时候,遇到 new User(),JVM 会根据 User 的全限定名去加载 User.class 。
2、JVM 会去本地磁盘查找 User.class 文件并加载 JVM 内存中。
3、JVM 通过调用类加载器自动创建这个类对应的 Class 对象,并且存储在 JVM 的方法区。注意:一个类有且只有一个 Class 对象。
5、使用反射
获取Class对象
1、使用 Class 类的 forName 静态方法
Class<?> person = Class.forName("test.bean.Person");
使用类的完全限定名来反射对象的类。常见的应用场景为:在 JDBC 开发中常用此方法加载数据库驱动。
2、直接获取某一个对象的 class
Class<Person> person = Person.class;
3、调用Object的getClass方法
Object 类中有 getClass 方法,因为所有类都继承 Object 类。从而调用 Object 类来获取:
Person person = new Person();
Class<? extends Person> personClass = person.getClass();
判断是否为某个类的实例
1、用 instanceof 关键字
2、用 Class 对象的 isInstance 方法(它是一个 Native 方法)
List<Integer> list = new ArrayList<>();
if (list instanceof List) {
System.out.println("instanceof----->true");
}
if (List.class.isInstance(list)) {
System.out.println("isInstance----->true");
}
创建实例
1、用 Class 对象的 newInstance 方法。
2、用 Constructor 对象的 newInstance 方法。
//第一种方式: Class 对象的 newInstance 方法
Class<?> person = Class.forName("test.bean.Person");
Person personInstance = (Person) person.newInstance();
//第二种方式 : Constructor 对象的 newInstance 方法
Constructor<?> constructor=person.getConstructor(String.class,Integer.class);
Person personInstance2 = (Person) constructor.newInstance("hucheng", 1);
获取对象的成员Field
Class 对象提供以下方法获取对象的成员(Field):
-
getFiled - 根据名称获取公有的(public)类成员。
-
getDeclaredField - 根据名称获取已声明的类成员。但不能得到其父类的类成员。
-
getFields - 获取所有公有的(public)类成员。
-
getDeclaredFields - 获取所有已声明的类成员。
Person person = new Person("HuCheng", 22); Class<Person> perClass = Person.class; //getFiled - 根据名称获取公有的(public)类成员。 Field name = perClass.getField("name"); System.out.println(name.get(person)); //getDeclaredField - 根据名称获取已声明的类成员。但不能得到其父类的类成员。 Field age = perClass.getDeclaredField("age"); age.setAccessible(true); System.out.println(age.get(person)); //getFields - 获取所有公有的(public)类成员。 Field[] fields = perClass.getFields(); for (Field field : fields) { System.out.println("getFields方法------->" + field.get(person)); } //getDeclaredFields - 获取所有已声明的类成员。 Field[] declaredFields = perClass.getDeclaredFields(); for (Field field : declaredFields) { field.setAccessible(true); System.out.println("getDeclaredFields方法------->" + field.get(person)); }
获取对象的方法Method
Class 对象提供以下方法获取对象的方法(Method):
-
getMethod - 返回类或接口的特定方法。其中第一个参数为方法名称,后面的参数为方法参数对应 Class 的对象。
-
getDeclaredMethod - 返回类或接口的特定声明方法。其中第一个参数为方法名称,后面的参数为方法参数对应 Class 的对象。
-
getMethods - 返回类或接口的所有 public 方法,包括其父类的 public 方法。
-
getDeclaredMethods - 返回类或接口声明的所有方法,包括 public、protected、默认(包)访问和 private 方法,但不包括继承的方法。
获取一个 Method 对象后,可以用 invoke 方法来调用这个方法。
Person person = new Person(); Class<Person> perClass = Person.class; // getMethod Method method = perClass.getMethod("publicMethod"); System.out.println(method.invoke(person)); // getDeclaredMethod Method declaredMethod = perClass.getDeclaredMethod("privateMethod", String.class); declaredMethod.setAccessible(true); System.out.println(declaredMethod.invoke(person, "arg1")); // getMethods Method[] methods = perClass.getMethods(); for (Method method2 : methods) { System.out.println("getMethods----------->" + method2.invoke(person)); } //getDeclaredMsethods Method[] declaredMethods = perClass.getDeclaredMethods(); for (Method method2 : declaredMethods) { method2.setAccessible(true); System.out.println(method2.invoke(person, "olleh")); }
获取对象的构造方法Constructor
Class 对象提供以下方法获取对象的构造方法(Constructor):
-
getConstructor - 返回类的特定 public 构造方法。参数为方法参数对应 Class 的对象。
-
getDeclaredConstructor - 返回类的特定构造方法。参数为方法参数对应 Class 的对象。
-
getConstructors - 返回类的所有 public 构造方法。
-
getDeclaredConstructors - 返回类的所有构造方法。