反射
类加载的过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。
加载
加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象(字节码对象),也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象(一个类只可能有一个字节码对象)。
现在 : 对象.getClass() == 类名.class --> 前面的对象是否属于后面的类型
以前的写法: 对象 instanceof 类
类的加载由类加载器(ClassLoader)完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类(类加载器的基类)来创建自己的类加载器[自定义类加载器]。
通过使用不同的类加载器,可以从不同来源加载类的二进制数据(字节码文件),通常有如下几种来源。
-
从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式。
-
从JAR包加载class文件,这种方式也是很常见的,JDBC编程时用到的数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。
-
通过网络加载class文件。
把一个Java源文件动态编译,并执行加载。类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。
链接
当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据(字节码对象)合并到JRE中。类连接又可分为如下3个阶段。
1)验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。Java是相对C++语言是安全的语言,例如Java有C++不具有的数组越界的检查。这本身就是对自身安全的一种保护。验证阶段是Java非常重要的一个阶段,它会直接的保证应用是否会被恶意入侵的一道重要的防线,越是严谨的验证机制越安全。验证的目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。其主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
四种验证做进一步说明:
文件格式验证:主要验证字节文件是否符合Class文件格式规范,并且能被当前的虚拟机加载处理。例如:主,次版本号是否在当前虚拟机处理的范围之内。常量池中是否有不被支持的常量类型。指向常量的中的索引值是否存在不存在的常量或不符合类型的常量。
元数据验证:对字节码描述的信息进行语义的分析,分析是否符合java的语言语法的规范。
字节码验证:最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要的针对元数据验证后对方法体的验证。保证类方法在运行时不会有危害出现。
符号引用验证:主要是针对符号引用转换为直接引用的时候,是会延伸到第三解析阶段,主要去确定访问类型等涉及到引用的情况,主要是要保证引用一定会被访问到,不会出现类等无法访问的问题。
2)准备:类准备阶段负责为类的静态变量分配内存,并设置默认初始值(系统默认值)。
**3)解析:**将类的二进制数据[字节码对象]中的符号引用替换成直接引用。说明一下:符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。
初始化
初始化是为类的静态变量赋予正确的初始值(程序员给静态成员变量赋的值),准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的,如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析,到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。
总结:
加载: 生成类的字节码对象
链接:
验证: 校验字节码对象中的成员是否符合Java类规范
准备: 加载类中静态成员并赋予系统默认值
解析: 把类中的符号引用替换成直接引用
初始化: 把类中静态成员的默认值替换成真实值;
类何时加载
- 创建类的实例,也就是new一个对象
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(Class.forName(“全类名”))
- 初始化一个类的子类(会首先初始化子类的父类)
- JVM启动时标明的启动类,即文件名和类名相同的那个类
类何时被加载: 类第一次被使用的时候加载
类加载器
类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载如JVM中,同一个类就不会被再次载入了。
- 根类加载器(bootstrap classloader):它用来加载 Java 的核心类,是用原生代码来实现的,并不继承自 java.lang.ClassLoader(负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
- 扩展类加载器(extensions classloader):它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null。
- 系统类加载器(system class loader):被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。
- 自定义类加载器(custom class loader): 我要指定一些类由某个类加载器加载 那么就可以自定义类记载器
类加载器加载Class的8个步骤[了解]
1. 检测此Class是否载入过,即在缓冲区中是否有此Class,如果有直接进入第8步,否则进入第2步。
2. 如果没有父类加载器,则要么Parent是根类加载器,要么本身就是根类加载器,则跳到第4步,如果父类加载器存在,则进入第3步。
3. 请求使用父类加载器去载入目标类,如果载入成功则跳至第8步,否则接着执行第5步。
4. 请求使用根类加载器去载入目标类,如果载入成功则跳至第8步,否则跳至第7步。
5. 当前类加载器尝试寻找Class文件,如果找到则执行第6步,如果找不到则执行第7步。
6. 从文件中载入Class,成功后跳至第8步。
7. 抛出ClassNotFountException异常。
8. 返回对应的java.lang.Class对象。
类加载机制-双亲委派模型
类加载器使用场景(重要)
类加载器的功能: 快速获取当前类所在模块src文件夹根目录下的文件 [开辟一个相对路径的起点]
//IO知识的相对路径起点 : 项目的根
//类加载器的相对路径起点 : 当前代码所在模块的src根目录
ClassLoader: 类加载器的类(类加载器的基类)
static ClassLoader getSystemClassLoader() : 获取到系统类加载器对象
InputStream getResourceAsStream(String fileName): 快速的获取传入文件路径指向的文件
String fileName: 文件路径 --> 相对路径写: 相对路径的起点是src文件夹
static InputStream getSystemResourceAsStream(String name):直接获取输入流对象 -> 推荐方法
//示例1
public static void main(String[] args) {
//目前没有自定义类加载器 -> 最底层的类加载器是 : 系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("systemClassLoader = " + systemClassLoader);
//系统类加载器 找其"父类"加载器 [逻辑继承] -> 拓展类加载器
ClassLoader extraClassLoader = systemClassLoader.getParent();
System.out.println("extraClassLoader = " + extraClassLoader);
//拓展类加载器 找其"父类"加载器 [逻辑继承] -> 根类加载器
ClassLoader rootClassLoader = extraClassLoader.getParent();
System.out.println("rootClassLoader = " + rootClassLoader);
}
//示例2
login.propertie :
username=admin
password=123456
public static void main(String[] args) throws IOException {
//IO : 相对路径起点 项目根目录
//BufferedReader br = new BufferedReader(
// new FileReader("Test\\src\\com\qh\classloader\login.propertie"));
//类加载器 : 相对路径的起点 : 当前模块的src根目录
// ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// InputStream is = systemClassLoader
// .getResourceAsStream("com\qh\classloader\login.propertie");
InputStream is = ClassLoader.
getSystemResourceAsStream("com\\qh\\classloader\\login.properties");
Properties properties = new Properties();
properties.load(is);
String username = properties.getProperty("username");
String password = properties.getProperty("password");
Scanner scanner = new Scanner(System.in);
System.out.println("请你输入您的账号 : ");
String uName = scanner.nextLine();
System.out.println("请您输入您的密码 : ");
String pwd = scanner.nextLine();
if (username.equals(uName) && password.equals(pwd)){
System.out.println("登录成功!");
}else{
System.out.println("登录失败!");
}
}
反射
反射 : 一种知识体系 [此知识体系在获取类中的成员并使用[构造方法,成员变量/常量,成员方法,注解...]]
反射是未来框架的底层源码使用的知识体系; //框架 : 帮助程序员快捷开发的代码
思想层面 :
以前你是普通工程师 : 直面需求
未来你是框架工程师 : 直面解决需求的人
代码层面 : //编写代码的手法
"正射" : 学生对象调用学习方法(几个小时) -> student.study(8);
反射 : 学习方法对象启动传入需要学习的学生对象和学习的时间 -> study方法对象.invoke(student,8);
学习反射的目的 : 是为了未来能看懂 框架 的底层源码;
反射使用的步骤
1. 获取类的字节码对象 [因为类的字节码对象中封装着类的成员]
2. 通过字节码对象获取类中的成员 [构造方法(Constructor),成员变量(Field),成员方法(Method)]
3. 类中成员对象自启动 [构造方法-> 创建对象,成员变量 -> 获取值/赋值.成员方法 -> 启动方法]
获取类的字节码对象[反射起手式]
1. 对象.getClass() [getClass()来自于Object类]
2. 类名.class [class属性 : 每个类型都有的属性(包含数组,包含基本数据类型)] //int[].class int数组的字节码对象, int.class 整型的字节码对象....
3. Class.forName("类的全类名") : 类的全类名-> 包名.类名
//示例
public static void main(String[] args) throws ClassNotFoundException {
//获取Student类的字节码对象
//1. 对象.getClass()
Student1 student1 = new Student1();
Class aClass1 = student1.getClass();
//2. 类名.class
Class aClass2 = Student1.class;
//3. Class.forName("全类名")
Class aClass3 = Class.forName("com.qh.reflect.Student1");
System.out.println("aClass1 = " + aClass1);
System.out.println("aClass2 = " + aClass2);
System.out.println("aClass3 = " + aClass3);
//一个类的字节码对象只可能有一个
System.out.println(aClass1 == aClass1);
System.out.println(aClass2 == aClass2);
System.out.println(aClass3 == aClass3);
}
获取类中成员
获取类中的构造方法对象
获取字节码对象中封装的构造方法对象 :
//只能获取public修饰的构造方法对象 -> 可以看到父类public修饰的构造方法
Constructor<?>[] getConstructors() : 获取类中所有的构造方法对象
Constructor<T> getConstructor(Class<?>... parameterTypes) : 获取类中指定的构造方法对象
//任何权限修饰符的构造方法对象都能获取 -> 弊端 : 不能获取父类的构造方法
Constructor<?>[] getDeclaredConstructors()
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
Class<?>... parameterTypes : 方法形参类型的字节码对象
获取类中的成员变量对象
获取字节码对象中封装的成员变量对象 :
Field[] getFields()
Field getField(String name)
//推荐
Field[] getDeclaredFields()
//String name : 要获取的成员变量的变量名
Field getDeclaredField(String name)
获取类中的成员变量对象
获取字节码对象中封装的成员变量对象 :
Field[] getFields()
Field getField(String name)
//推荐
Field[] getDeclaredFields()
//String name : 要获取的成员变量的变量名
Field getDeclaredField(String name)
获取类中的成员方法对象
获取字节码对象中封装的成员方法对象 :
Method[] getMethods()
Method getMethod(String name)
//推荐
Method[] getDeclaredFields()
//String name : 要获取的成员变量的变量名
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
使用类中成员
如果该成员是没有访问权限的,虽然Declared系列方法能够获取此成员对象,但是真正使用时需要开启暴力反射开关!
void setAccessible(boolean flag) : 开启暴力反射开关 //只需要在使用没有权限的成员之前,开启暴力反射开关即可
使用类中构造方法对象[创建对象]
T newInstance(Object... initargs) : 使用构造方法对象创建对象
返回值T : 得到的对象
Object... initargs : 此构造方法对象启动需要的实际参数 [形参和实参匹配]
//在Class类中有一个快捷创建此类对象的方法 [启动的是类中的无参构造(使用前提是: 类中必须有无参构造)]
//Class 类中的方法 : T newInstance()
Student student = clazz.newInstance();
System.out.println("student = " + student);
使用类中成员变量对象[设置值和获取值]
获取值的方法 :
Object get(Object obj)
返回值 Object : 拿到的成员变量的值
形参 Object obj : 你要获取的是哪个对象的该属性值
设置值的方法 :
void set(Object obj, Object value)
形参 Object obj : 你要设置的是哪个对象的该属性值
形参 Object value : 你要设置的值
//示例1:构造方法
public class Student1 {
private String name;
int age;
public String address;
protected String sex;
public Student1() {
System.out.println("无参构造启动了");
}
private Student1(String name) {
System.out.println("private-"+name);
}
Student1(String name, int age){
System.out.println("默认-"+name+"-"+age);
}
public Student1(String name, int age, String address) {
System.out.println("public-"+name+"-"+age+"-"+address);
}
protected Student1(int age, String sex){
System.out.println("protected-"+age+"-"+sex);
}
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class<Student1> student1Class = Student1.class;
// 返回一个包含 Constructor对象的数组,反映由该Class对象表示的类的所有 [公共] 构造函数
Constructor<?>[] constructors = student1Class.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("constructor = " + constructor);
}
System.out.println("-------------");
// 返回由该 Class对象表示的类声明的 [所有] 构造函数的 Constructor对象的数组
Constructor[] declaredConstructors = student1Class.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println("declaredConstructor = " + declaredConstructor);
}
System.out.println("-------------");
// 返回一个 Constructor对象,该对象反映由该Class对象表示的类的指定 [公共] 构造函数
// 参数:要获取的构造方法的参数的个数及数据类型对应的字节码文件对象
Constructor<Student1> declaredConstructor1 = student1Class.getDeclaredConstructor();
System.out.println("declaredConstructor1 = " + declaredConstructor1);
Constructor<Student1> declaredConstructor2 = student1Class.getDeclaredConstructor(String.class);
System.out.println("declaredConstructor2 = " + declaredConstructor2);
Constructor<Student1> declaredConstructor3 = student1Class.getDeclaredConstructor(String.class,int.class);
System.out.println("declaredConstructor3 = " + declaredConstructor3);
Constructor<Student1> declaredConstructor4 = student1Class.getDeclaredConstructor(String.class,int.class,String.class);
System.out.println("declaredConstructor4 = " + declaredConstructor4);
Constructor<Student1> declaredConstructor5 = student1Class.getDeclaredConstructor(int.class,String.class);
System.out.println("declaredConstructor5 = " + declaredConstructor5);
System.out.println("-------------");//在Class类中有一个快捷创建此类对象的方法 [启动的是类中的无参构造(使用前提是: 类中必须有无参构造)]
//Class 类中的方法 : T newInstance()
Student1 student10 = student1Class.newInstance();
System.out.println("student0 = " + student10);
//根据构造方法对象获取此类的对象
Student1 student0 = declaredConstructor1.newInstance();
System.out.println("student0 = " + student0);
declaredConstructor2.setAccessible(true);//开启暴力反射开关
Student1 student2 = declaredConstructor2.newInstance("张三");//没有访问权限,不能直接访问private
System.out.println("student2 = " + student2);
Student1 student3 = declaredConstructor3.newInstance("李四", 23);
System.out.println("student3 = " + student3);
Student1 student4 = declaredConstructor4.newInstance("王五", 24, "上海");
System.out.println("student4 = " + student4);
Student1 student5 = declaredConstructor5.newInstance( 24, "男");
System.out.println("student5 = " + student5);
System.out.println("-------------");
}
//示例2:成员变量
public class Student2 {
private String name;
int age;
public String address;
protected String sex;
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Student2 Student2 = new Student2();
Class student2Class = Student2.getClass();
返回一个包含Field对象的数组,Field对象反映由该Class对象表示的类或接口的所有可访问的 [公共] 的成员变量
Field[] fields = student2Class.getFields();
for (Field field : fields) {
System.out.println("field = " + field);
}
System.out.println("----------------");
//返回一个包含Field对象的数组,Field对象反映由该Class对象表示的类或接口的 [所有] 成员变量
Field[] declaredFields = student2Class.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("declaredField = " + declaredField);
}
System.out.println("----------------");
//获取单个类中成员变量对象
Field name = student2Class.getDeclaredField("name");
name.setAccessible(true);
Field age = student2Class.getDeclaredField("age");
Field address = student2Class.getDeclaredField("address");
Field sex = student2Class.getDeclaredField("sex");
System.out.println("name = " + name);
System.out.println("age = " + age);
System.out.println("address = " + address);
System.out.println("sex = " + sex);
System.out.println("----------------");
//赋值
name.setAccessible(true);//开启暴力反射开关
name.set(Student2,"张三");
age.set(Student2,23);
address.set(Student2,"上海");
sex.set(Student2,"男");
//获取值
Object nameValue = name.get(Student2);
Object ageValue = age.get(Student2);
Object addressValue = address.get(Student2);
Object sexValue = sex.get(Student2);
System.out.println("nameValue = " + nameValue);
System.out.println("ageValue = " + ageValue);
System.out.println("addressValue = " + addressValue);
System.out.println("sexValue = " + sexValue);
}
//示例3:成员方法
public class Student3 {
public void S1(){
System.out.println("public");
}
protected void S2(String name){
System.out.println("protected-"+name);
}
String S3(){
System.out.println("默认");
return "默认";
}
private void S4(int age){
System.out.println("private-"+age);
}
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Student3 student3 = new Student3();
Class<? extends Student3> student3Class = student3.getClass();
//返回一个包含方法对象的数组,方法对象反映由该Class对象表示的类或接口的所有 公共方法
//包括由类或接口声明的对象以及从超类和超级接口继承的类
Method[] methods = student3Class.getMethods();
for (Method method : methods) {
System.out.println("method = " + method);
}
System.out.println("-----------------------");
//返回一个包含方法对象的数组,方法对象反映由该Class对象表示的类或接口的所有声明方法
//包括:public\protected\default(package)访问和私有方法,但不包括继承方法
Method[] declaredMethods = student3Class.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("declaredMethod = " + declaredMethod);
}
System.out.println("-----------------------");
//单独获取成员方法对象
Method s1 = student3Class.getDeclaredMethod("S1");
Method s2 = student3Class.getDeclaredMethod("S2",String.class);
Method s3 = student3Class.getDeclaredMethod("S3");
Method s4 = student3Class.getDeclaredMethod("S4",int.class);
System.out.println("s1 = " + s1);
System.out.println("s2 = " + s2);
System.out.println("s3 = " + s3);
System.out.println("s4 = " + s4);
System.out.println("-----------------------");
System.out.println("s1.invoke(student3) = " + s1.invoke(student3));
System.out.println("s2.invoke(student3,\"张三\") = " + s2.invoke(student3, "张三"));
System.out.println("s3.invoke(student3) = " + s3.invoke(student3));
s4.setAccessible(true);//开启暴力反射开关
System.out.println("s4.invoke(student3,23) = " + s4.invoke(student3, 23));
}
//示例4:
info. :
className=com.qh.reflect.Test
methodName=show
public class Test {
public void show(){
System.out.println("show()方法执行了");
}
public void method(){
System.out.println("method()方法执行了");
}
}
public class TestDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//获取配置文件中的信息
InputStream systemResourceAsStream = ClassLoader
.getSystemResourceAsStream("com\\qh\\reflect\\info.properties");
//一键加载到属性集里
Properties properties = new Properties();
properties.load(systemResourceAsStream);
//获取信息
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
systemResourceAsStream.close();
//反射代码
//获取类的字节码对象
Class classForName = Class.forName(className);
//创建此类对象
Object name = classForName.newInstance();
//获取类中成员方法对象
Method declaredMethod = classForName.getDeclaredMethod(methodName);
//启动方法
declaredMethod.invoke(name);
}
}