反射
类的加载时机
- 当程序要使用某个类时,如果该类还未被加载到内存中
- 系统会通过加载,连接,初始化三步来实现对这个类进行初始化
- 加载:就是指将
class
文件读入内存,并为之创建一个Class
对象。任何类被使用时系统都会建立一个Class
对象- 连接:验证是否有正确的内部结构,并和其他类协调一致,准备负责为类的静态成员分配内存,并设置默认的初始化值
- 初始化:初始化成员变量等等
加载时机
- 创建类的实例
- 访问类的静态变量,或者为静态变量赋值
- 调用类的静态方法
- 初始化某个类的子类
- 使用反射方式来强制创建某个类或接口对应的
java.lang.Class
对象
类加载器
- 🐤什么是类加载器
classLoader
负责将 .class
文件加载到内存中,并为之生成对应的 Class
对象,虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行
类加载器分类
根类加载器
- 也被称为引导类加载器,负责 Java 核心类的加载
- 比如
System
,String
等。在 JDK 中JRE
的lib
目录下rt.jar
文件中
扩展类加载器
- 负责
JRE
的扩展目录中jar
包的加载- 在 JDK 中 JRE 的
lib
目录下ext
目录
系统类加载器
- 负责在
JVM
启动时加载来自java
命令的class
文件- 以及
classpath
环境变量所指定的jar
包和类路径
什么是反射
🐤创建一个对象的三个阶段
- 源文件阶段
.java
的文件- 字节码阶段
.class
- 创建对象阶段
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);
}
}
}
最终的文件结构如下图所示,然后运行测试类即可