Java反射机制

Java反射机制概述

  • Reflection(反射)是被视为==动态语言==的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接==操作任意对象的内部属性及方法==。
  • 加载完类之后,在堆内存的方法区中就产生了一个Class类类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。==我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。==

正常方式:引入需要的“包类”名称—>通过new实例化—>取得实例化对象

反射方式:实例化对象—>getClass()方法—>得到完整的“包类”名称

反射相关的主要API

  • ==java.lang.Class==:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造器
  • ……

反射简单示例

定义一个person类

package com.miao.java;

public class Person {
private String name;
public int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//私有 带参数 的构造器
private Person(String name) {
this.name = name;
}
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 "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void show(){
System.out.println("hello,world");
}
//私有方法
private String nation(String nation){
System.out.println("我的国籍是:"+nation);
return nation;
}
}

定义一个测试类

package com.miao.java;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionTest1 {
//反射之前 对于 person的操作
@Test
public void test1(){
//创建一个对象
Person p = new Person("miao",22);
//调用对象的属性和方法(public才可以调用)
p.age=11;
System.out.println(p.toString());
p.show();
//在person类外部,不能通过person类的对象调用其内部私有构造,比如,私有属性和方法
}
//反射之后,对于person的操作
@Test
public void test2() throws Exception{
Class clazz = Person.class;
//通过反射,创建person对象
Constructor constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("tom", 12);
Person p = (Person) obj;
System.out.println(p.toString());
//通过反射,调用对象的属性和方法
//调用属性 getDeclaredField()
Field age = clazz.getDeclaredField("age");
age.set(p,10);
System.out.println(p.toString());
//调用方法 getDeclaredMethod()
Method show = clazz.getDeclaredMethod("show");
show.invoke(p);

//通过反射,可以调用person类的私有构造,比如:私有构造器,方法,属性
//调用私有构造器
Constructor constructor1 = clazz.getDeclaredConstructor(String.class);
constructor1.setAccessible(true);
Person p1 = (Person) constructor1.newInstance("ttom");
System.out.println(p1.toString());
//调用私有属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1,"jjru");
System.out.println(p1);
//调用私有方法
Method nation = clazz.getDeclaredMethod("nation", String.class);
nation.setAccessible(true);
String nation1 = (String) nation.invoke(p1,"中国");
System.out.println(nation1);
}
}

test2()的结果

image-20230103223523860

理解Class类并获取Class实例

关于java.lang.Class类的理解

  1. 类的加载过程
    • 程序经过javac.exe命令,生成一个或多个字节码文件(.class结尾)
    • 使用java.exe命令对某个字节码文件解释运行,相当于将字节码文件加载到内存中。(==此过程称为类的加载==)
    • ==加载到内存中的类,称为运行时类,作为Class的一个实例==
  2. Class的实例对应着一个运行时类
  3. 加载到内存中的运行时类,会缓存一定的时间,在此时间,我们可以通过不同的方式来获取此运行时类

获取Class实例的方式

    @Test
public void test3() throws ClassNotFoundException {
//方式一 调用运行时类的属性 .class
Class clazz = Person.class;
System.out.println(clazz);
//方式二 调用运行时类的对象,调用getClass()方法
Person p = new Person();
Class clazz1 = p.getClass();
System.out.println(clazz1);
//方式三 调用Class的静态方法,forName(String classPath)--------常用
Class clazz2 = Class.forName("com.miao.java.Person");
System.out.println(clazz2);
// Class clazz3 = Class.forName("java.lang.String");
// System.out.println(clazz3);
System.out.println("=====================");
System.out.println(clazz == clazz1);//true
System.out.println(clazz == clazz2);//true
//方式四 调用类的加载器,ClassLoader
ClassLoader classLoader = ReflectionTest1.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.miao.java.Person");
System.out.println(clazz4);
System.out.println(clazz == clazz4);//true
}

类的加载与ClassLoader的理解

类的加载过程

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

  1. 类的加载(Load)
    • 将类的class文件读入内存,并为之创建一个java.lang.Class对象,==此过程由类加载器完成==
  2. 类的链接(Link)
    • 将类的二进制数据合并到JRE中
  3. 类的初始化(Initialize)
    • JVM负责对类进行初始化
  1. 源程序(*.java文件)
  2. 经过Java编译器
  3. 字节码(*.class文件)
  4. 类装载器
  5. 字节码效验器
  6. 解释器
  7. 操作系统平台

了解ClassLoader

类加载器的作用

类加载器的作用:用来把类(class)装载进内存的,JVM规范定义了如下的类的加载器

  • Bootstap ClassLoader

    引导类加载器:

    JVM自带的类加载器,负责java平台核心库

    负责加载Java安装目录下的/jre/lib类库(核心类库)至JVM中,

    不继承java.lang.ClassLoader,不可以被Java程序直接调用,

    本身是用C++写的

  • Extension ClassLoader

    扩展类加载器:

    负责加载Java安装目录下的/jre/lib/ext类库(扩展类库)至JVM

    Java程序可直接调用

  • Application ClassLoader

    应用程序类加载器:

    负责加载CLASSPATH路径下的类库,

    我们写的类就是通过这个加载器完成加载,

    可以通过 ClassLoader.getSystemClassLoader()来获取这个加载器

  • 自定义类加载器

img

//简单了解  类加载器
@Test
public void test(){
//调用当前类的 getClassLoader()
//使用系统类加载器进行加载 sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//调用系统类加载器的getParent(),获得扩展类加载器 sun.misc.Launcher$ExtClassLoader@5a07e868
ClassLoader parent = classLoader.getParent();
System.out.println(parent);
//调用扩展类加载器,无法获取 引导类加载器 null
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
}

读取配置文件

方式一

@Test
public void test1() throws Exception {
//读取配置文件方式一
Properties pros = new Properties();
FileInputStream fil = new FileInputStream("src/main/resources/jdbc.properties");
pros.load(fil);

String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user:"+user+"password:"+password);

}
@Test
public void test2() throws IOException {
//类加载器 读取配置文件方式二
Properties pros = new Properties();
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc.properties");
pros.load(is);

String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user:"+user+"----password:"+password);
}

创建运行时类的对象

通过反射创建运行时类的对象

//通过反射创建运行时类的对象
@Test
public void test() throws InstantiationException, IllegalAccessException {
Class clazz = Person.class;
/*
newInstance() 调用此方法,创建对应的运行时类的对象,内部调用了运行时类的空参的构造方法
*/
Object obj = clazz.newInstance();
System.out.println(obj);

}

简单了解反射的动态性

//体验反射的动态性
@Test
public void test1(){
for (int i = 0;i<100;i++){
int num = new Random().nextInt(3);
String classPath = "";
switch (num){
case 0:
classPath="java.util.Date";
break;
case 1:
classPath="java.lang.Object";
break;
case 2:
classPath="com.miao.java.Person";
break;
}
try {
Object obj = getInstance(classPath);
System.out.println(obj);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/*
创建一个指定的类
classPath:指定类的全类名
*/
public Object getInstance(String classPath) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}

结果

image-20230104231207848

获取运行时类的完整结构

待续…

调用运行时类的指定结构

反射的应用:动态代理