反射

类的加载时机

  • 当程序要使用某个类时,如果该类还未被加载到内存中
  • 系统会通过加载,连接,初始化三步来实现对这个类进行初始化
  • 加载:就是指将 class 文件读入内存,并为之创建一个 Class 对象。任何类被使用时系统都会建立一个 Class 对象
  • 连接:验证是否有正确的内部结构,并和其他类协调一致,准备负责为类的静态成员分配内存,并设置默认的初始化值
  • 初始化:初始化成员变量等等

加载时机

  • 创建类的实例
  • 访问类的静态变量,或者为静态变量赋值
  • 调用类的静态方法
  • 初始化某个类的子类
  • 使用反射方式来强制创建某个类或接口对应的 java.lang.Class 对象

类加载器

  • 🐤什么是类加载器 classLoader

负责将 .class 文件加载到内存中,并为之生成对应的 Class 对象,虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行

类加载器分类

根类加载器

  • 也被称为引导类加载器,负责 Java 核心类的加载
  • 比如 System, String 等。在 JDK 中 JRElib 目录下 rt.jar 文件中

扩展类加载器

  • 负责 JRE 的扩展目录中 jar 包的加载
  • 在 JDK 中 JRE 的 lib 目录下 ext 目录

系统类加载器

  • 负责在 JVM 启动时加载来自 java 命令的 class 文件
  • 以及 classpath 环境变量所指定的 jar 包和类路径

什么是反射

🐤创建一个对象的三个阶段

  1. 源文件阶段 .java 的文件
  2. 字节码阶段 .class
  3. 创建对象阶段 new 对象名称

反射

  • JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法
  • 对于任意一个对象,都能够调用它的任意一个方法和属性
  • 这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制
  • 想要使用反射,就必须得要获取字节码文件

获取字节码文件

  • Object 类的 getClass() 方法:判断两个对象是否是同一个字节码文件
  • 静态属性 class:当作静态方法的锁对象
  • Class 类中静态方法 forName():读取配置文件

如下是一个获取字节码的小 Demo 创建 Person.java

/**
 * @author BNTang
 **/
public class Person {
}

创建 Client.java 测试类

/**
 * @author BNTang
 **/
public class Client {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> c = Class.forName("top.it6666.demo1.Person");

        Class<Person> personClass = Person.class;

        Person person = new Person();
        Class<? extends Person> ps = person.getClass();

        System.out.println(c == personClass);
        System.out.println(personClass == ps);
    }
}

通过字节码创建对象

修改之前创建的 Person.java

/**
 * @author BNTang
 **/
public class Person {
    public String name;
    public Integer age;
    private String gender;

    public Person() {
        super();
    }

    public Person(String name, Integer age, String gender) {
        super();
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public void show() {
        System.out.println("我是" + this.name + "年龄" + this.age + "性别" + this.gender);
    }

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

通过无参构造创建对象

  • 获取字节码
  • 调用字节码的 newInstance() 方法
/**
 * @author BNTang
 **/
public class Client {
    public static void main(String[] args) throws Exception {
        // 1.获取字节码
        Class<?> c = Class.forName("top.it6666.demo1.Person");

        // 2.通过字节码创建对象
        // 从JDK9开始过时了
        // Person p = (Person)clazz1.newInstance();
        Person p = (Person)c.getDeclaredConstructor().newInstance();
        p.name = "zs";
        p.age = 23;
        
        p.show();
    }
}

通过有参构造创建对象

  • 获取字节码的构造器,clazz.getConstructor(type.class) 因为在反射阶段操作的都是字节码,不知道具体的类型,只有在创建对象的时候才去给实际参数
  • 通过构造器创建对象,调用构造器的 newInstance 方法并传入参数
/**
 * @author BNTang
 **/
public class Client {
    public static void main(String[] args) throws Exception {
        // 1.获取字节码
        Class<?> c = Class.forName("top.it6666.demo1.Person");

        // 2.获取字节码的构造器
        Constructor<?> cs = c.getConstructor(String.class, Integer.class, String.class);

        // 3.通过构造器来创建对象
        Person person = (Person)cs.newInstance("镜", 23, "女");
        person.show();
    }
}

获取字段

获取公共的字段

/**
 * @author BNTang
 **/
public class Client {
    public static void main(String[] args) throws Exception {
        // 1.获取字节码
        Class<?> c = Class.forName("top.it6666.demo1.Person");

        // 2.获取字节码的构造器
        Constructor<?> cs = c.getConstructor(String.class, Integer.class, String.class);

        // 3.通过构造器来创建对象
        Person person = (Person)cs.newInstance("镜", 23, "女");

        // 获取公共字段
        Field name = c.getField("name");
        name.set(person, "诸葛亮");

        person.show();
    }
}

获取私有的字段

/**
 * @author BNTang
 **/
public class Client {
    public static void main(String[] args) throws Exception {
        // 1.获取字节码
        Class<?> c = Class.forName("top.it6666.demo1.Person");

        // 2.获取字节码的构造器
        Constructor<?> cs = c.getConstructor(String.class, Integer.class, String.class);

        // 3.通过构造器来创建对象
        Person person = (Person)cs.newInstance("镜", 23, "女");

        // 获取私有字段
        Field gender = c.getDeclaredField("gender");

        // 去除私有的权限
        gender.setAccessible(true);
        gender.set(person, "未知");

        person.show();
    }
}

获取方法

获取公共方法

/**
 * @author BNTang
 **/
public class Client {
    public static void main(String[] args) throws Exception {
        // 1.获取字节码
        Class<?> c = Class.forName("top.it6666.demo1.Person");

        // 2.获取字节码的构造器
        Constructor<?> cs = c.getConstructor(String.class, Integer.class, String.class);

        // 3.通过构造器来创建对象
        Person person = (Person)cs.newInstance("镜", 23, "女");

        // 获取公共方法
        Method show = c.getMethod("show");
        System.out.println(show.getName());
        show.invoke(person);
    }
}

获取私有方法

/**
 * @author BNTang
 **/
public class Client {
    public static void main(String[] args) throws Exception {
        // 1.获取字节码
        Class<?> c = Class.forName("top.it6666.demo1.Person");

        // 2.获取字节码的构造器
        Constructor<?> cs = c.getConstructor(String.class, Integer.class, String.class);

        // 3.通过构造器来创建对象
        Person person = (Person)cs.newInstance("镜", 23, "女");

        // 获取私有方法
        Method eat = c.getDeclaredMethod("eat", String.class);

        // 去除私有权限
        eat.setAccessible(true);
        eat.invoke(person, "面条");
    }
}

越过数组泛型检测

  • 数组如果定义好了泛型就不能添加泛型以外的类型
  • 可以通过反射来去实现添加以外的类型
  • 在一个 Integer 泛型的数组当中添加字符串类型
/**
 * @author BNTang
 **/
public class Client {
    public static void main(String[] args) throws Exception {
        List<Integer> list = new ArrayList<>();
        list.add(23);

        // 越过之前
        System.out.println(list);

        Class<?> clazz = Class.forName("java.util.ArrayList");
        Method method = clazz.getMethod("add", Object.class);
        method.invoke(list, "BNTang");

        // 越过之后
        System.out.println(list);
    }
}

Servlet 创建过程

需求

  • 通过反射根据 xml 配置文件,创建对象

xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0"
         metadata-complete="true">
    <absolute-ordering/>
    <display-name>web</display-name>
    <servlet>
        <!-- 自己起一个名称  -->
        <servlet-name>bntang</servlet-name>
        <servlet-class>top.it6666.servlet.FirstServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <!-- 必须和上面名称一致 -->
        <servlet-name>bntang</servlet-name>
        <url-pattern>/firstServlet</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

创建 Servlet 类

创建 IServlet.java 接口

/**
 * @author BNTang
 **/
public interface IServlet {
    /**
     * 初始化
     */
    void init();
}
/**
 * @author BNTang
 **/
public class FirstServlet implements IServlet {
    @Override
    public void init() {
        System.out.println("init 🐤");
    }
}

通过 xml 读取配置创建对象

使用 dom4j 解析,下载地址:https://wwe.lanzoui.com/iOLWirydtwf

/**
 * @author BNTang
 **/
public class Client {
    public static void main(String[] args) throws Exception {
        SAXReader reader = new SAXReader();
        Document doc = reader.read("src/web.xml");

        // 获取根元素
        Element rootElement = doc.getRootElement();
        System.out.println(rootElement);

        List<Element> list = rootElement.elements("servlet");

        for (Element servEle : list) {
            Element classEle = servEle.element("servlet-class");
            System.out.println(classEle.getText());

            // 根据类名可以创建字节码
            Class<?> c = Class.forName(classEle.getText());
            Object obj = c.getDeclaredConstructor().newInstance();
            Method m = c.getMethod("init");
            m.invoke(obj);
        }
    }
}

最终的文件结构如下图所示,然后运行测试类即可

posted @ 2021-07-27 19:45  BNTang  阅读(301)  评论(0编辑  收藏  举报