【Java面试】谈谈你对自定义类加载器的理解

简介: 【Java面试】谈谈你对自定义类加载器的理解

为什么需要自定义类加载器

首先介绍自定义类的应用场景:

(1)加密:Java代码可以轻易的被反编译,如果你需要把自己的代码进行加密以防止反编译,可以先将编译后的代码用某种加密算法加密,类加密后就不能再用Java的ClassLoader去加载类了,这时就需要自定义ClassLoader在加载类的时候先解密类,然后再加载。

(2)从非标准的来源加载代码:如果你的字节码是放在数据库、甚至是在云端,就可以自定义类加载器,从指定的来源加载类。

(3)以上两种情况在实际中的综合运用:比如你的应用需要通过网络来传输 Java 类的字节码,为了安全性,这些字节码经过了加密处理。这个时候你就需要自定义类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出在Java虚拟机中运行的类。

双亲委派模型

在实现自己的ClassLoader之前,我们先了解一下系统是如何加载类的,那么就不得不介绍双亲委派模

型的实现过程。

//双亲委派模型的工作过程源码
protected synchronized Class<?> loadClass(String name, boolean resolve) throws
ClassNotFoundException{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
}
catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
//父类加载器无法完成类加载请求
}
if (c == null) {
// If still not found, then invoke findClass in order to find the
class
//子加载器进行类加载
c = findClass(name);
}
}
if (resolve) {
//判断是否需要链接过程,参数传入
resolveClass(c);
}
return c;
}

双亲委派模型的工作过程如下:

(1)当前类加载器从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。

(2)如果没有找到,就去委托父类加载器去加载(如代码c = parent.loadClass(name, false)所示)。

父类加载器也会采用同样的策略,查看自己已经加载过的类中是否包含这个类,有就返回,没有就委托父类的父类去加载,一直到启动类加载器。因为如果父加载器为空了,就代表使用启动类加载器作为父加载器去加载。

(3)如果启动类加载器加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),则会抛出一个异常ClassNotFoundException,然后再调用当前加载器的findClass()方法进行加载。

双亲委派模型的好处:

(1)主要是为了安全性,避免用户自己编写的类动态替换 Java的一些核心类,比如 String。

(2)同时也避免了类的重复加载,因为 JVM中区分不同类,不仅仅是根据类名,相同的 class文件被不同的ClassLoader加载就是不同的两个类。

自定义类加载器

(1)从上面源码看出,调用loadClass时会先根据委派模型在父加载器中加载,如果加载失败,则会调用当前加载器的findClass来完成加载。

(2)因此我们自定义的类加载器只需要继承ClassLoader,并覆盖findClass方法,下面是一个实际例

子,在该例中我们用自定义的类加载器去加载我们事先准备好的class文件。

自定义一个People.java类做例子

public class People {
  //该类写在记事本里,在用javac命令行编译成class文件,放在d盘根目录下
  private String name;
  public People() {}
  public People(String name) {
  this.name = name;
  }
  public String getName() {
  return name;
  }
  public void setName(String name) {
  this.name = name;
  }
  public String toString() {
  return "I am a people, my name is " + name;
  }
}

自定义类加载器

自定义一个类加载器,需要继承ClassLoader类,并实现findClass方法。其中defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class(只要二进制字节流的内容符合Class文件规范)。

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
public class MyClassLoader extends ClassLoader
{
  public MyClassLoader()
  {
  }
  public MyClassLoader(ClassLoader parent)
  {
  super(parent);
  }
  protected Class<?> findClass(String name) throws ClassNotFoundException
  {
  File file = new File("D:/People.class");
  try{
    byte[] bytes = getClassBytes(file);
    //defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
    Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
    return c;
  }
  catch (Exception e)
  {
    e.printStackTrace();
    }
    return super.findClass(name);
  }
  private byte[] getClassBytes(File file) throws Exception
  {
  // 这里要读入.class的字节,因此要使用字节流
  FileInputStream fis = new FileInputStream(file);
  FileChannel fc = fis.getChannel();
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  WritableByteChannel wbc = Channels.newChannel(baos);
  ByteBuffer by = ByteBuffer.allocate(1024);
  while (true){
    int i = fc.read(by);
    if (i == 0 || i == -1)
    break;
    by.flip();
    wbc.write(by);
    by.clear();
    }
    fis.close();
    return baos.toByteArray();
    }
  }

在主函数里使用

MyClassLoader mcl = new MyClassLoader();
Class<?> clazz = Class.forName("People", true, mcl);
Object obj = clazz.newInstance();
System.out.println(obj);
System.out.println(obj.getClass().getClassLoader());//打印出我们的自定义类加载器

结果如下

至此关于自定义ClassLoader的内容总结完毕。


相关文章
|
2月前
|
算法 Java
50道java集合面试题
50道 java 集合面试题
|
28天前
|
存储 Java 索引
用Java语言实现一个自定义的ArrayList类
自定义MyArrayList类模拟Java ArrayList核心功能,支持泛型、动态扩容(1.5倍)、增删改查及越界检查,底层用Object数组实现,适合学习动态数组原理。
79 4
|
3月前
|
Java 数据库 C++
Java异常处理机制:try-catch、throws与自定义异常
本文深入解析Java异常处理机制,涵盖异常分类、try-catch-finally使用、throw与throws区别、自定义异常及最佳实践,助你写出更健壮、清晰的代码,提升Java编程能力。
|
4月前
|
Java 数据库连接 数据库
Java 相关知识点总结含基础语法进阶技巧及面试重点知识
本文全面总结了Java核心知识点,涵盖基础语法、面向对象、集合框架、并发编程、网络编程及主流框架如Spring生态、MyBatis等,结合JVM原理与性能优化技巧,并通过一个学生信息管理系统的实战案例,帮助你快速掌握Java开发技能,适合Java学习与面试准备。
204 2
Java 相关知识点总结含基础语法进阶技巧及面试重点知识
|
2月前
|
算法 Java
50道java基础面试题
50道java基础面试题
|
4月前
|
缓存 Java 关系型数据库
Java 面试经验总结与最新 BAT 面试资料整理含核心考点的 Java 面试经验及最新 BAT 面试资料
本文汇总了Java面试经验与BAT等大厂常见面试考点,涵盖心态准备、简历优化、面试技巧及Java基础、多线程、JVM、数据库、框架等核心技术点,并附实际代码示例,助力高效备战Java面试。
152 0
|
4月前
|
缓存 Cloud Native Java
Java 面试微服务架构与云原生技术实操内容及核心考点梳理 Java 面试
本内容涵盖Java面试核心技术实操,包括微服务架构(Spring Cloud Alibaba)、响应式编程(WebFlux)、容器化(Docker+K8s)、函数式编程、多级缓存、分库分表、链路追踪(Skywalking)等大厂高频考点,助你系统提升面试能力。
217 0
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?