类加载机制与反射
一.虚拟机类加载机制
1.虚拟机类加载机制概述
-
虚拟机把描述类的数据从class文件加载到内存
-
将类的数据进行校验,转换解析和初始化
-
形成可以被java虚拟机直接使用的java类型
2.类加载过程
- 当程序要使用某个类时,如果该类还未被加载到内存中,系统会通过加载,连接,初始化三步来实现对这个类的加载.
(1)加载
- 就是指将class文件读入内存,并为之创建一个Class对象.任何类被使用时系统都会建立一个Class对象
(2)链接
- 验证是否有正确的内部结构,并和其他类协调一致;准备负责为类的静态成员分配内存,并设置默认初始化值解析将类的二进制数据中的符号引用替换为直接引用—> 方便计算机 以后寻址
(3)初始化
- 在使用时需要初始化
3.类的初始化时机
- 第一次创建类的实例
- 类的静态成员使用
- 类的静态方法调用
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类 —> 主方法运行时 会自动的加载主方法所在的类的字节码文件
二.类加载器
1.类加载器概述
- 类加载器是负责加载类的对象,将class文件(硬盘)加载到内存中,并为之生成对应的java.lang.Class对象.
(1)例
Hello.class------->类加载器------>Hello.class
- Hello:这个类的类对象 (就是Class类的对象)
- 类对象: 字节码信息形成的对象(包含了这个类中所有的信息) —> 是 Class 类 类型的对象
2.类加载器的分类
(1)分类
类加载器 | 分类 | 描述 |
---|---|---|
Bootstrap ClassLoader | 引导类 | 是所有加载器的父加载器也被称为根类加载器,负责Java核心类的加载,比如System,String等. |
Extension ClassLoader | 扩展类 | 负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下ext目录. |
Application ClassLoade | 系统类 | 加载自己写的类 ,负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径. |
自定义类 | 开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求. |
(2)继承关系
-Bootstrap ClassLoader
-Extension ClassLoader
-Application ClassLoader
3.双亲委派机制
- 一有事 就找爹 爹做不了的 自己再干
(1)双亲委派机制描述
- 指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器.每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载.
(2)双亲委派模型工作过程
a.
- 当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成.
b.
- 当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成.
c.
- 如果Bootstrap ClassLoader加载失败(在<JAVA_HOME>\lib中未找到所需类),就会让Extension ClassLoader尝试加载.
d.
- 如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载.
e.
- 如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载.
f.
- 如果均加载失败,就会抛出ClassNotFoundException异常.
(3)例子
- 当一个Hello.class这样的文件要被加载时.不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了.如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法.父类中同理会先检查自己是否已经加载过,如果没有再往上.注意这个过程,直到到达Bootstrap classLoader之前,都是没有哪个加载器自己选择加载的.如果父加载器无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException.
4.CLassLoader类
(1)ClassLoader类概述
- ClassLoader 叫做类加载器.虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流” 也就是.class字节码文件,这个动作放到java虚拟机外部去实现,以便让应用程序自己决定去如何获取所需要的类,实现这个动作的代模块称之为“类加载器”.
(2)ClassLoader方法
方法 | 描述 |
---|---|
static ClassLoader getSystemClassLoader() | 返回用于委派的系统类加载器 |
ClassLoader getParent() | 返回父类加载器进行委派 |
(3)方法示例
package com.lfg.test;
public class Test02 {
public static void main(String[] args) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
System.out.println(cl); //AppClassLoader
System.out.println(cl.getParent()); //ExtClassLoader
System.out.println(cl.getParent().getParent()); //null
}
}
三.反射应用
1.反射机制的概述
- 反射是指在运行时去获取一个类的变量和方法信息.然后通过获取到的信息来创建对象,调用方法的一种机制.
- 由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍然可以扩展.
2.获取Class类的对象
(1)描述
- Class类: Class类型的实例 表示正在运行的java应用程序的类或者接口 类对象.
- Class类的对象: 想获取和操作类中的内容,首先要获取类的字节码对象(Class类对象),每一个正在运行的类,都有对应的字节码对象,获取了类的字节码对象,就可以使用这个对象的所有方法,这些方法都定义在Class类型中.
(2)示例
a.Student类
package com.lfg.pojo;
import java.util.Objects;
public class Student {
private String sname;
private String sgender;
private int sage;
public int sid;
public Student() {
}
private Student(String sname) {
this.sname = sname;
}
private Student(String sname, String sgender) {
this.sname = sname;
this.sgender = sgender;
}
public Student(String sname, String sgender, int sage) {
this.sname = sname;
this.sgender = sgender;
this.sage = sage;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public String getSgender() {
return sgender;
}
public void setSgender(String sgender) {
this.sgender = sgender;
}
public int getSage() {
return sage;
}
public void setSage(int sage) {
this.sage = sage;
}
public int getSid() {
return sid;
}
public void setSid(int sid) {
this.sid = sid;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return sage == student.sage && Objects.equals(sname, student.sname) && Objects.equals(sgender, student.sgender);
}
@Override
public int hashCode() {
return Objects.hash(sname, sgender, sage);
}
@Override
public String toString() {
return "Student{" +
"sname='" + sname + '\'' +
", sgender='" + sgender + '\'' +
", sage=" + sage +
'}';
}
public void printInfo(){
toString();
}
public void prinInfoFamle(String famle){
System.out.println("famle=>"+famle);
}
private void pirntInfoName(String sname){
System.out.println("sname=>"+sname);
}
private void pirntInfoPrivate(){
toString();
}
}
b.测试类
package com.lfg.test;
import com.lfg.pojo.Student;
public class Test03 {
public static void main(String[] args) {
Student student = new Student();
try {
//获取Student类的字节码对象
Class c1 = Student.class;
Class c2 = student.getClass();
Class c3 = Class.forName("com.lfg.pojo.Student");
System.out.println(c1 == c2);
System.out.println(c2 == c3);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
3.反射获取构造方法
(1)方法
方法 | 描述 |
---|---|
Constructor<?>[] getConstructors() | 返回所有公共构造方法对象的数组 |
Constructor<?>[] getDeclaredConstructors() | 返回所有构造方法对象的数组 |
Constructor getConstructor(Class<?>… parameterTypes) | 返回单个公共构造方法对象 |
Constructor getDeclaredConstructor(Class<?>…parameterTypes) | 返回单个构造方法对象 |
(2)Constructor类型
a.描述
- 表示构造方法类型,这个类的每个对象,都是一个确定的,具体的构造方法
- 构造方法对象应该具有的功能: 获取构造方法各种信息(构造方法修饰符、构造方法名称、构造方法的参数列表、构造方法的注解),最基本的一个功能就是,创建对象.
b.Constructor类用于创建对象的方法
方法 | 描述 |
---|---|
T newInstance(Object…initargs) | 根据指定的构造方法创建对象,参数为所运行构造方法需要的实际参数. |
(3)示例
package com.lfg.test;
import com.lfg.pojo.Student;
import java.lang.reflect.Constructor;
import java.util.Arrays;
public class Test04 {
public static void main(String[] args) {
Student student = new Student();
//获取Student类有哪些属性,哪些构造方法,哪些一般方法?
try {
//获取Student类的字节码对象
Class c1 = Student.class;
Class c2 = student.getClass();
Class c3 = Class.forName("com.lfg.pojo.Student");
//获取Student类的公共的构造方法
Constructor constructor1[] = c3.getConstructors();
System.out.println(Arrays.toString(constructor1));
System.out.println("-----------------------------------------");
//获取Student类所有的构造方法
Constructor constructor2[] = c3.getDeclaredConstructors();
System.out.println(Arrays.toString(constructor2));
System.out.println("-----------------------------------------");
//获取某公共的无参的构造方法
Constructor constructor3 = c3.getConstructor();
System.out.println(constructor3);
System.out.println("-----------------------------------------");
//获取某公共的有参的构造方法
Constructor constructor4 = c3.getConstructor(String.class,String.class,int.class);
System.out.println(constructor4);
System.out.println("-----------------------------------------");
//获取某私有的有参的构造方法
Constructor constructor5 = c3.getDeclaredConstructor(String.class);
System.out.println(constructor5);
System.out.println("-----------------------------------------");
//使用某公共的无参的构造方法创建对象
Student stu1 = (Student)constructor3.newInstance();
stu1.setSname("mary");
System.out.println(stu1.getSname());
System.out.println("-----------------------------------------");
//使用某公共的有参的构造方法创建对象
Student stu2 = (Student) constructor4.newInstance("范冰冰","female",1);
System.out.println(stu2.getSname());
System.out.println("-----------------------------------------");
//使用某私有的有参的构造方法创建对象
//使用暴力反射干掉private
constructor5.setAccessible(true);
Student stu3 = (Student)constructor5.newInstance("刘仔");
System.out.println(stu3.getSage()+","+stu3.getSname());
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.反射获取成员变量
(1)方法
方法 | 描述 |
---|---|
Field[] getFields()Field[] getFields() | 返回所有公共成员变量对象的数组 |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
Field getField(String name) | 返回单个公共成员变量对象 |
Field getDeclaredField(String name) | 返回单个成员变量对象 |
- Class类获取成员变量对象
- Field类型
- 表示:一个成员变量类型,每个对象都是一个具体的成员变量
- 作用: 获取成员变量的各种信息(修饰符、注解、名称),做各种数据类型的转换
- Field类用于给成员变量赋值的方法
- set(Object obj, Object value):用于给obj对象的,该成员变量,赋value值
- Field类获取成员变量值的方法
- get(Object obj): 用于获取obj对象的指定成员变量值
(2)示例
package com.lfg.test;
import com.lfg.pojo.Student;
import java.lang.reflect.Field;
import java.util.Arrays;
public class Test05 {
public static void main(String[] args) {
Student student = new Student();
//获取Student类有哪些属性,哪些构造方法,哪些一般方法?
try {
//获取Student类的字节码对象
Class c1 = Student.class;
Class c2 = student.getClass();
Class c3 = Class.forName("com.lfg.pojo.Student");
//获取Student类的公共的成员变量
Field field1[] = c3.getFields();
System.out.println(Arrays.toString(field1));
System.out.println("-----------------------------------");
//获取Student类所有的成员变量
Field field2[] = c3.getDeclaredFields();
System.out.println(Arrays.toString(field2));
System.out.println("-----------------------------------");
//获取Student类的某公共的成员变量
Field field3 = c3.getField("sid");
System.out.println(field3);
System.out.println("-----------------------------------");
//获取Student类的某私有的成员变量
Field field4 = c3.getDeclaredField("sname");
System.out.println(field4);
System.out.println("-----------------------------------");
//使用Student类的某公共的成员变量
field3.set(student,1); //student.setSid(1);
System.out.println(field3.get(student)); //student.getSid()
System.out.println("-----------------------------------");
//使用Student类的某私有的成员变量
field4.setAccessible(true);
field4.set(student,"蔡坤");
System.out.println(field4.get(student));
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.获取类中的成员方法
(1)方法
方法 | 描述 |
---|---|
Method[] getMethods() | 返回所有公共成员方法对象的数组,包括继承的 |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,不包括继承的 |
Method getMethod(String methodName, Class<?>…parameterTypes) | 返回单个公共成员方法对象 |
Method getDeclaredMethod(String methodName, Class<?>…parameterTypes) | 返回单个成员方法对象 |
- Class类获取成员方法对象
- Method类型
- 表示:成员方法的类型,该类型的每个对象,都是一个具体的成员方法
- 作用:获取成员方法信息,运行方法
- Method类用于执行方法的功能
- invoke(Object obj, Object…values)
- 调用obj对象的成员方法
- 参数是values
- 返回值是Object类型
- invoke(Object obj, Object…values)
(2)示例
package com.lfg.test;
import com.lfg.pojo.Student;
import java.lang.reflect.Method;
import java.util.Arrays;
public class Test06 {
public static void main(String[] args) {
Student student = new Student();
//获取Student类有哪些属性,哪些构造方法,哪些一般方法?
try {
//获取Student类的字节码对象
Class c1 = Student.class;
Class c2 = student.getClass();
Class c3 = Class.forName("com.lfg.pojo.Student");
//获取Student类的公共的成员方法(包括继承来的)
Method method1[] = c3.getMethods();
System.out.println(Arrays.toString(method1));
System.out.println("---------------------------------");
//获取Student类的所有的成员方法
Method method2[] = c3.getDeclaredMethods();
System.out.println(Arrays.toString(method2));
System.out.println("---------------------------------");
//获取Student类的某公共的成员方法
Method method3 = c3.getMethod("printInfo");
System.out.println(method3);
System.out.println("---------------------------------");
//获取Student类的某私有的成员方法
Method method4 = c3.getDeclaredMethod("pirntInfoPrivate");
System.out.println(method4);
System.out.println("---------------------------------");
//使用Student类的某公共的成员方法
method3.invoke(student); //student.eat();
System.out.println("---------------------------------");
//使用Student类的某私有的成员方法
method4.setAccessible(true);
method4.invoke(student);
} catch (Exception e) {
e.printStackTrace();
}
}
}
6.暴力反射
(1)概述
- 通过Class类中getDeclaredXXX方法以获取类中的所有声明的成员(属性、方法、构造),私有的成员也可以获取到.但是私有成员进行访问使用时,会因为权限问题导致失败,因此就需要暴力反射解决访问私有的问题
(2)修改对象的访问权限
- AccessibleObject类是Field,Method和Constructor对象的基类. 它提供了将反射对象标记为在使用它时抑制默认Java语言访问控制检查的功能.
- setAccessible(boolean flag)
- true的值表示反射对象应该在使用时抑制Java语言访问检查
- false的值表示反映的对象应该强制执行Java语言访问检查
- 一旦设定当前对象可以访问,私有的成员也可以被访问,被修改
四.反射实现多方法请求
1.BaseServlet
package com.lfg.servlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class BaseServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
String flag = req.getParameter("flag");
//获取当前类的字节码对象
Class clazz = this.getClass();
try {
//flag其实就是要执行的方法的名字
//所有需要执行的方法的参数都是request和response
//根据方法名及其参数的数据类型获取要执行的方法
Method method = clazz.getDeclaredMethod(flag,HttpServletRequest.class,HttpServletResponse.class);
method.setAccessible(true);
method.invoke(this,req,resp);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
2.使用
- 使用要求:
- 前端必须传递flag数据,且该数据的值必须和servlet中要执行的方法名字一致
- servlet中所有需要执行的方法的参数必须是request和response
public class XxxServlet extends BaseServlet{
}
五.分页示例
1.分页概述
-
当前页页码:page
-
每页显示数据条数:rows
-
从哪一条开始取(偏移量):index = (page-1)*rows
-
数据总条数:countRows
-
总页数:countPages
-
上一页页码:prevPage,如果当前页是第一页,需要判断
-
下一页页码:nextPage,如果当前页是最后一页,需要判断
2.PageUtil工具类
package com.lfg.utils;
public class PageUtil {
private int page; //当前页页码
private int rows; //每页显示条数
private int index; //偏移量
private int countRows; //总条数
private int countPages; //总页数
private int prevPage; //当前页的上一页页码
private int nextPage; //当前页的下一页页码
public PageUtil(String page,int rows,int countRows){
init_page(page);
this.rows = rows;
init_index();
this.countRows = countRows;
init_countPages();
init_prevPage();
init_nextPage();
}
private void init_page(String page){
if (page == null || "".equals(page)) {
this.page = 1;
} else {
this.page = Integer.parseInt(page);
}
}
private void init_index(){
this.index = (this.page - 1) * this.rows;
}
private void init_countPages(){
int mod = this.countRows % this.rows;
if (mod == 0) {
this.countPages = this.countRows / this.rows;
} else {
this.countPages = this.countRows / this.rows + 1;
}
}
private void init_prevPage(){
if (this.page == 1) {
this.prevPage = 1;
} else {
this.prevPage = this.page - 1;
}
}
private void init_nextPage(){
if (this.page == this.countPages) {
this.nextPage = this.countPages;
} else {
this.nextPage = this.page + 1;
}
}
public int getPage() {
return page;
}
public int getRows() {
return rows;
}
public int getIndex() {
return index;
}
public int getCountRows() {
return countRows;
}
public int getCountPages() {
return countPages;
}
public int getPrevPage() {
return prevPage;
}
public int getNextPage() {
return nextPage;
}
}
3.index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
恭喜您,登录成功<br>
<a href="studentServlet?flag=getStudentByPage&page=1">展示学生信息</a>
</body>
</html>
4.Dao层
(1)StudentDao
int getCountRows();
List<Student> getStudentByPage(PageUtil pageUtil);
(2)StudentDaoImpl
@Override
public int getCountRows() {
int countRows = 0;
String sql = "select count(*) from student";
try {
countRows = (int)(long)queryRunner.query(sql, new ScalarHandler());
} catch (SQLException e) {
e.printStackTrace();
}
return countRows;
}
@Override
public List<Student> getStudentByPage(PageUtil pageUtil) {
List<Student> list = null;
String sql = "select * from student limit ?,?";
try {
list = queryRunner.query(sql, new BeanListHandler<>(Student.class),pageUtil.getIndex(),pageUtil.getRows());
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
5.Service层
(1)StudentService
int getCountRows();
List<Student> getStudentByPage(PageUtil pageUtil);
(2)StudentServiceImpl
@Override
public int getCountRows() {
return studentDao.getCountRows();
}
@Override
public List<Student> getStudentByPage(PageUtil pageUtil) {
return studentDao.getStudentByPage(pageUtil);
}
6.StudentServlet
protected void getStudentByPage(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//分页数据查询
String page = req.getParameter("page");
int rows = 10;
int countRows = studentService.getCountRows();
PageUtil pageUtil = new PageUtil(page, rows, countRows);
List<Student> list = studentService.getStudentByPage(pageUtil);
req.setAttribute("stuList",list);
req.setAttribute("pageUtil",pageUtil);
req.getRequestDispatcher("student.jsp").forward(req,resp);
}
7.Student.jsp
六.挂载图片
1.学生表添加头像列
ALTER TABLE student ADD sphoto VARCHAR(50);
2.修改实体类
private String sphoto;