跳至主要內容

17. 反射

景天大约 10 分钟

学习目标:

  • 熟悉类加载过程与类加载器
  • 熟练掌握类加载时机
  • 掌握如何获取字节码文件对象
  • 掌握使用配置文件
  • 掌握Class,Constructor,Field,Method使用
  • 熟悉了解自定义类加载器与双亲委派模型(有能力的同学掌握)

类加载

之前学的

image-20221022112026823
image-20221022112026823

过程

  • 加载

    • 通过类加载器(ClassLoader)加载.class文件,读取到内存
    • 在这个过程中,生成这个类所对应的字节码文件对象(java.lang.Class)
  • 链接

    • 验证: 对字节码文件格式的验证(aced babe 咖啡宝贝 魔法数字)

    • 准备: 给类的静态成员分配内存并赋予默认初始值

      • static int a =10;
        
    • 解析: 把符号引(用一组符号来描述被引用的目标)用转化为直接引用(真实的地址)

    • class Student{
       String name;
       int age;
       Subject subject;
       
      }
      
      class Subject{
      	String name;
      }
      
  • 初始化

    • 给静态成员赋真实的值, 并且执行静态代码块中的内容

类加载器

分类

Bootstrap ClassLoader 根类加载器 负责Java运行时核心类的加载,JDK中JRE的lib目录下rt.jar

Extension ClassLoader 扩展类加载器 负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下ext目录

Sysetm(App) ClassLoader 系统类加载器/应用加载器 负责加载自己定义的Java类

package _23reflect.com.cskaoyan._01introduction;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/22 11:42
 **/

public class Demo {
    public static void main(String[] args) {
        // 查看类加载器
        // 系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);

        // 扩展类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);

        // 根类加载器 null 不是java写的
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);

        // 看一下加载路径
        System.out.println(System.getProperty("java.class.path")
                .replace(";", System.lineSeparator()));
        System.out.println("-------");
        System.out.println(System.getProperty("java.ext.dirs")
                .replace(";", System.lineSeparator()));


    }
}

双亲委派模型

image-20221024094028570
image-20221024094028570

类加载时机

创建类的实例(首次创建该类对象)

访问类的静态变量(首次)

调用类的静态方法(首次)

加载某个类的子类,会先触发父类的加载

直接使用java.exe命令来运行某个主类,也就是执行了某个类的main()方法

使用反射方式来强制创建某个类或接口对应的java.lang.Class对象

java代码的3个阶段

image-20221024095115934
image-20221024095115934

反射

什么是反射

获取运行时类信息的一种手段

反射的起点是字节码文件对象

获取字节码文件对象的几种方式

  • 对象.getClass()
  • 类名.class
  • Class.forName(String className) 全限定名
  • ClassLoader里的loadClass(String className)

注意:

无论通过什么方式获取的字节码文件对象 都是同一个

Demo

package _23reflect.com.cskaoyan._02cls;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/24 9:58
 **/
/*
获取字节码文件对象的几种方式
 */
public class Demo {
    public static void main(String[] args) throws ClassNotFoundException {
        // 对象.getClass
        A a = new A();
        Class<? extends A> c1 = a.getClass();

        // 类名.class
        Class<A> c2 = A.class;

        System.out.println(c1 == c2);

        // Class.forName(全限定名)
        Class<?> c3 = Class.forName("_23reflect.com.cskaoyan._02cls.A");

        System.out.println(c1 == c3);

        // ClassLoader.loadClass(String className)
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        Class<?> c4 = systemClassLoader.loadClass("_23reflect.com.cskaoyan._02cls.A");
        System.out.println(c1 == c4);

    }
}

class A{

}

注意

package _23reflect.com.cskaoyan._02cls;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/24 10:08
 **/

public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 类名.class
        // 没有执行静态代码块
        //Class<B> c1 = B.class;

        // Class.forName()
        // 执行静态代码块
        Class<?> c2 = Class.forName("_23reflect.com.cskaoyan._02cls.B");
        
    }
}

class B{
    static {
        System.out.println("静态代码块执行了!");
    }
}

关于Class

Class 类的实例表示正在运行的 Java 应用程序中的类和接口

Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。

image-20221024101228387
image-20221024101228387

配置文件(.properties)

配置文件的几种格式.properties .xml .yml

配置文件的作用: 放配置信息的 (数据库的, 第三方服务的配置信息)

.properties的格式 键值对(key-value) key=value key是不能重复的

注释是# 文件里面全是String

image-20221024101640808
image-20221024101640808

获取配置信息

Properties类

Properties 类表示了一个持久的属性集。Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。

构造方法

Properties() 创建一个无默认值的空属性列表。

成员方法

voidload(InputStream inStream) 从输入流中读取属性列表(键和元素对)。
voidload(Reader reader) 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。
StringgetProperty(String key) 用指定的键在此属性列表中搜索属性。
package _23reflect.com.cskaoyan._03config;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/24 10:27
 **/

public class Demo {
    public static void main(String[] args) throws IOException {
        // 创建Properties对象
        Properties properties = new Properties();
        // load
        properties.load(new FileInputStream("config.properties"));

        // 获取属性值
        // getProperty(String key)
        String port = properties.getProperty("port");
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String host = properties.getProperty("host");
        System.out.println(port);
        System.out.println(password);
        System.out.println(user);
        System.out.println(host);

    }
}

package _23reflect.com.cskaoyan._03config;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Properties;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/24 10:27
 **/

public class Demo2 {
    public static void main(String[] args) throws IOException {
        // 创建Properties对象
        Properties properties = new Properties();

        // 通过类加载器
        URL systemResource = ClassLoader.getSystemResource("");
        System.out.println(systemResource);

        InputStream in = ClassLoader.getSystemResourceAsStream("config.properties");


        // load
        properties.load(in);

        // 获取属性值
        // getProperty(String key)
        String port = properties.getProperty("port");
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String host = properties.getProperty("host");
        System.out.println(port);
        System.out.println(password);
        System.out.println(user);
        System.out.println(host);



    }
}

有中文的情况

package _23reflect.com.cskaoyan._03config;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/24 11:26
 **/

public class Demo3 {
    public static void main(String[] args) throws IOException {
        // 创建Properties对象
        Properties properties = new Properties();

        // load
        properties.load(
                new InputStreamReader(
                        new FileInputStream("config.properties"),"GBK"));

        // 获取属性
        String user = properties.getProperty("user");
        System.out.println(user);
    }
}

通过反射获取构造方法(Constructor)

通过反射获取所有构造方法

Constructor[] getConstructors()
Constructor[] getDeclaredConstructors()

获取指定构造方法

Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

使用Constructor创建对象

Person p = new Person("zs",20,true)
newInstance(参数列表)

暴力破解

setAccessible(true) 
package _23reflect.com.cskaoyan._04api;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/24 11:34
 **/

public class ConstructorTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 反射技术的起点 获取字节码文件对象
        Class<?> stuCls = Class.forName("_23reflect.com.cskaoyan.bean.Person");

        System.out.println("获取所有的public的构造方法----");
        // Constructor[] getConstructors()
        Constructor<?>[] constructors = stuCls.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }

        System.out.println("获取所有的构造方法----");

        //Constructor[] getDeclaredConstructors()
        Constructor<?>[] declaredConstructors = stuCls.getDeclaredConstructors();
        for (Constructor<?> constructor : declaredConstructors) {
            System.out.println(constructor);
        }
        System.out.println("获取指定的public的构造方法----");
        // Constructor<T> getConstructor(Class<?>... parameterTypes)
        Constructor<?> constructor = stuCls.getConstructor(String.class, int.class, boolean.class);
        //Constructor<?> constructor = stuCls.getConstructor(String.class, int.class);
        // java.lang.NoSuchMethodException


        System.out.println(constructor);
        System.out.println("获取指定的构造方法----");
        //Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
        Constructor<?> declaredConstructor = stuCls.getDeclaredConstructor(String.class, int.class);
        System.out.println(declaredConstructor);

        // 使用构造方法对象创建对象
        // newInstance
        Object o = constructor.newInstance("zs", 20, true);
        System.out.println(o);

        // java.lang.IllegalAccessException
        // setAccessible(true)  忽略java语法检查
        declaredConstructor.setAccessible(true);
        Object o1 = declaredConstructor.newInstance("ls", 21);
        System.out.println(o1);


    }
}

通过反射获取成员变量(Field)

通过反射获取所有成员变量

Field[] getFields()
Field[] getDeclaredFields()

获取指定成员变量

Field getField(String name)
Field getDeclaredField(String name)

通过Field读写对象的成员变量(可暴力破解)

Object get(Object obj):获取值,传入对象
void set(Object obj, Object value):赋值,传入对象
package _23reflect.com.cskaoyan._04api;

import _23reflect.com.cskaoyan.bean.Person;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/24 14:25
 **/
/*
反射获取成员变量

 */
public class FieldTest {
    public static void main(String[] args) throws Exception{
        // 获取字节码文件对象
        Class<?> personCls = Class.forName("_23reflect.com.cskaoyan.bean.Person");
        System.out.println("获取所有的public的成员变量------");
        // Field[] getFields()
        Field[] fields = personCls.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
        System.out.println("获取所有的成员变量------");

        //Field[] getDeclaredFields()
        Field[] declaredFields = personCls.getDeclaredFields();
        for (Field field : declaredFields) {
            System.out.println(field);
        }
        System.out.println("获取指定的public的成员变量------");
        // Field getField(String name)
        Field nameField = personCls.getField("name");
        System.out.println(nameField);
        System.out.println("获取指定的成员变量------");

        //Field getDeclaredField(String name)
        Field ageField = personCls.getDeclaredField("age");
        System.out.println(ageField);

        // 给成员变量赋值 获取成员变量的值
        //void set(Object obj, Object value):赋值,传入对象
        Constructor<?> declaredConstructor = personCls.getDeclaredConstructor();
        // 实例化对象
        Object o = declaredConstructor.newInstance();
        nameField.set(o, "zs");
        System.out.println(o);

        ageField.setAccessible(true);
        ageField.set(o, 22);
        System.out.println(o);
        // Object get(Object obj):获取值,传入对象
        Object o1 = nameField.get(o);
        System.out.println(o1);
    }
}

通过反射获取成员方法(Method)

获取所有成员方法

Method[] getMethods()// 父类的也能获取到
Method[] getDeclaredMethods()

获取指定的成员方法

Method getMethod(String name, Class<?>... parameterTypes)
Method getDeclaredMethod(String name, Class<?>... parameterTypes)

利用Method调用对象的方法

Object invoke(Object obj, Object... args)
package _23reflect.com.cskaoyan._04api;

import _23reflect.com.cskaoyan.bean.Person;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/24 14:39
 **/
/*
通过反射获取方法
 */
public class MethodTest {
    public static void main(String[] args) throws Exception{
        // 获取字节码文件对象
        Class<?> personCls = Class.forName("_23reflect.com.cskaoyan.bean.Person");

        System.out.println("获取所有的public的方法----");
        // Method[] getMethods()
        Method[] methods = personCls.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("获取所有的方法----");

        //Method[] getDeclaredMethods()
        Method[] declaredMethods = personCls.getDeclaredMethods();
        for (Method method : declaredMethods) {
            System.out.println(method);
        }

        System.out.println("获取指定的public的方法----");
        // Method getMethod(String name, Class<?>... parameterTypes)
        Method eatMethod1 = personCls.getMethod("eat");
        System.out.println(eatMethod1);

        System.out.println("获取指定的方法----");

        //Method getDeclaredMethod(String name, Class<?>... parameterTypes)
        Method eatMethod2 = personCls.getDeclaredMethod("eat", String.class);
        System.out.println(eatMethod2);

        //Person p = new Person();
        //p.eat();

        // 反射调用方法
        // Object invoke(Object obj, Object... args)
        Constructor<?> declaredConstructor = personCls.getDeclaredConstructor();
        Object o = declaredConstructor.newInstance();
        Object invoke = eatMethod1.invoke(o);
        System.out.println(invoke);

        eatMethod2.setAccessible(true);
        eatMethod2.invoke(o, "apple");

    }
}

补充

其他API

可以通过Class直接实例化 , 但是要有一个无参构造方法

package _23reflect.com.cskaoyan._04api;

import java.lang.reflect.Constructor;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/24 11:48
 **/

public class Demo {
    public static void main(String[] args) throws Exception{
        Class<?> c = Class.forName("_23reflect.com.cskaoyan._04api.A");

        // 通过class对象直接实例化对象
        Object o = c.newInstance();
        System.out.println(o);
    }
}

class A {
    int a;

    public A(int a) {
        this.a = a;
    }

    public A() {
    }
}

其他API

package _23reflect.com.cskaoyan.bean;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/24 11:31
 **/

public class Person {
    // 定义成员变量
    public  String name;
    private int age;
    boolean gender;

    // 构造方法

    public Person(String name, int age, boolean gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    private Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person() {
    }

    // 定义成员方法

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

    public String eat(){
        System.out.println("eat food!");
        return "吃了";
    }

    private void eat(String food){
        System.out.println("eat " + food);
    }
}


package _23reflect.com.cskaoyan._05add;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/24 14:57
 **/

public class OtherApi {
    public static void main(String[] args) throws Exception{
        // 获取字节码文件对象
        Class<?> c = Class.forName("_23reflect.com.cskaoyan.bean.Person");
        //Class<?> c = Class.forName("java.io.OutputStream");
        // 获取全限定类名
        System.out.println(c.getName());
        // 获取简单名称
        System.out.println("c.getSimpleName() = " + c.getSimpleName());
        // 获取父类
        Class<?> superclass = c.getSuperclass();
        System.out.println("superclass = " + superclass.getSimpleName());

        // 获取实现的接口
        Class<?>[] interfaces = c.getInterfaces();
        for (Class<?> i : interfaces) {
            System.out.println(i);
        }
        // 获取类加载器
        ClassLoader classLoader = c.getClassLoader();
        System.out.println("classLoader = " + classLoader);

        // 获取name这个成员变量对象
        Field nameField = c.getDeclaredField("name");
        // 获取权限修饰符
        int modifiers = nameField.getModifiers();
        System.out.println(modifiers);
        // static String toString(int mod)
        // 返回描述指定修饰符中的访问修饰符标志的字符串。
        String s = Modifier.toString(modifiers);
        System.out.println("s = " + s);

        // 获取eat(String s)方法对象
        Method eatMethod = c.getDeclaredMethod("eat", String.class);
        Class<?> returnType = eatMethod.getReturnType();
        System.out.println("returnType = " + returnType);
        Class<?>[] parameterTypes = eatMethod.getParameterTypes();
        for (Class<?> parameterType : parameterTypes) {
            System.out.println(parameterType.getSimpleName());
        }
    }
}


自定义类加载器

  • 继承ClassLoader
  • 重写findClass方法
package _23reflect.com.cskaoyan._05add;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/24 15:20
 **/

// 继承ClassLoader
public class MyClassLoader extends ClassLoader{
    // 定义成员变量
    String classPath;

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }
    // 重写findClass()


    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        Class<?> aClass = null;
        // 怎么把数据装到byte[] ?
        // 读取.class文件
        try {
            byte[] data = getData();
            // 字节码文件对象从哪来 defineClass
            // protected  Class<?> defineClass(String name, byte[] b, int off, int len)
            //将一个 byte 数组转换为 Class 类的实例。
            aClass = defineClass(name, data, 0, data.length);
        } catch (IOException e) {
            e.printStackTrace();
        }


        // 最终要返回这个类对应的字节码文件对象
        return aClass;
    }

    private byte[] getData() throws IOException {
        // 读取Class文件到数组里
        FileInputStream in = new FileInputStream(classPath);
        // 借助于 ByteArrayOutputStream
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // 此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。
        // 可使用 toByteArray() 和 toString() 获取数据。
        // 边读边写
        int readCount;
        byte[] bytes = new byte[1024];
        while ((readCount = in.read(bytes)) != -1) {
            out.write(bytes, 0, readCount);
        }
        // toByteArray()
        byte[] bytes1 = out.toByteArray();

        return bytes1;
    }
}


package _23reflect.com.cskaoyan._05add;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @description:
 * @author: 景天
 * @date: 2022/10/24 15:27
 **/

public class Demo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        // 定义要加载的文件的路径
        String classPath = "D:\\Log.class";
        // 创建自定义的类加载器对象
        MyClassLoader myClassLoader = new MyClassLoader(classPath);
        // 加载类loadClass(String name)
        // 得到字节码文件对象
        Class<?> logCls = myClassLoader.loadClass("Log");
        // 看一下是哪个类加载器加载
        ClassLoader classLoader = logCls.getClassLoader();
        System.out.println(classLoader);
        // 拿到方法对象
        Method method = logCls.getDeclaredMethod("func");
        Object o = logCls.newInstance();
        // 调用方法invoke
        method.invoke(o);
    }
}

反射应用场景

通过反射获取注解信息

动态代理

ORM(Object Relational Mapping)框架, 数据库框架

image-20221024163122140
image-20221024163122140